SpringSecurity权限管理系统实战—五、整合SpringSecurity(下)
系列目录
前言
上篇文章SpringSecurity整合了一半,这次把另一半整完,所以本篇的序号接着上一篇。
七、自定义用户信息
前面我们登录都是用的指定的用户名和密码或者是springsecurity默认的用户名和打印出来的密码。我们要想连接上自定义数据库只需要实现一个自定义的UserDetailsService。
我们新建一个JwtUserDto继承UserDetails并实现它的方法
@Data
@AllArgsConstructor
public class JwtUserDto implements UserDetails {
    //用户数据
    private MyUser myUser;
	//用户权限的集合
    @JsonIgnore
    private List<GrantedAuthority> authorities;
    public List<String> getRoles() {
        return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
    }
	//加密后的密码
    @Override
    public String getPassword() {
        return myUser.getPassword();
    }
	//用户名
    @Override
    public String getUsername() {
        return myUser.getUserName();
    }
	//是否过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
	//是否锁定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
	//凭证是否过期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
	//是否可用
    @Override
    public boolean isEnabled() {
        return myUser.getStatus() == 1 ? true : false;
    }
}
自定义一个UserDetailsServiceImpl实现UserDetailsService
@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserService userService;
    @Autowired
    private MenuDao menuDao;
    @Override
    public JwtUserDto loadUserByUsername(String userName) throws UsernameNotFoundException {
        MyUser user = userService.getUser(userName);//根据用户名获取用户
        if (user == null ){
            throw new UsernameNotFoundException("用户名不存在");//这个异常一定要抛
        }else if (user.getStatus().equals(MyUser.Status.LOCKED)) {
            throw new LockedException("用户被锁定,请联系管理员");
        }
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        List<MenuIndexDto> list = menuDao.listByUserId(user.getId());
        List<String> collect = list.stream().map(MenuIndexDto::getPermission).collect(Collectors.toList());
        for (String authority : collect){
            if (!("").equals(authority) & authority !=null){
                GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority);
                grantedAuthorities.add(grantedAuthority);
            }
        }//将用户所拥有的权限加入GrantedAuthority集合中
        JwtUserDto loginUser =new JwtUserDto(user,grantedAuthorities);
        return loginUser;
    }
}
这里在获取权限的时候遇到了个小小的坑,就是mybatis数据里的空值和null,在你从未对这个数据修改时,它就是null。如果修改了又删除掉了,它就会是空值。
meudao中的listByUserId方法
 @Select("SELECT DISTINCT sp.id,sp.parent_id,sp.name,sp.icon,sp.url,sp.type,sp.permission  " +
            "FROM my_role_user sru " +
            "INNER JOIN my_role_menu srp ON srp.role_id = sru.role_id " +
            "LEFT JOIN my_menu sp ON srp.menu_id = sp.id " +
            "WHERE " +
            "sru.user_id = #{userId}")
    @Result(property = "title",column = "name")
    @Result(property = "href",column = "url")
    List<MenuIndexDto> listByUserId(@Param("userId")Integer userId);
八、加密
老话题来聊一聊,加密的重要性。
2011年国内某开发者社区(可不就是csdn吗)被攻击数据库,600多万明文存储的用户账号被公开,大量用户隐私泄露。
这是个老梗了,几乎每篇说加密重要性的博文中,csdn的事就要被拿出来遛一遛。
那么为什么密码加密怎么重要??因为在你的数据库被攻击泄露了数据时,如果你的密码也被黑客掌握,那么即使你修复好了数据库泄露的问题,黑客手上仍然还有着用户的密码(总不能要求所有用户修改密码吧)
所以我们需要在系统开发之初就尽量的避免这种问题。
那么说了这么多,怎么来加密呢?
其实在SpringSecurity种已经内置了密码的加密机制,只需要实现一个PasswordEncoder接口即可。
来看一下源码
public interface PasswordEncoder {
    String encode(CharSequence var1);
    boolean matches(CharSequence var1, String var2);
    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}
- encode():把参数按照特定的解析规则进行解析。
- matches()验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回 true;如果不匹配,则返回 false。
- upgradeEncoding():如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回 false。默认返回 false。
第一个参数表示需要被解析的密码。第二个参数表示存储的密码。
Spring Security 还内置了几种常用的 PasswordEncoder 接口,官方推荐使用的是BCryptPasswordEncoder
。我们来配置一下。在SpringConfig种添加如下代码。
	@Autowired
    private UserDetailsService userDetailsService;
	@Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }//自定义userDetailsService加密
是不是十分简单,我们再重启项目,这时候控制台就不再打印密码,现在需要输入数据库中的用户名密码才能登录。
九、获取用户信息
之前我们在绘制菜单时,把用户的id给写死了。现在我们要从SpringSecurity中来获取用户信息。
有两种方法获取已登录用户的信息,一种是从session中拿,另一种就是SpringSecurity提供的方法。这里选择后一种方法。
我们可以通过以下方法来获取登录后用户的信息(其余还有获取登录ip等方法,不多介绍)
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
我们转换下类型
JwtUserDto jwtUserDto = (JwtUserDto)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
打印一下jwtUserDto,看到我们确实拿到了用户的信息
那么我们改写下通过用户id获取菜单这个方法
 	@GetMapping(value = "/index")
    @ResponseBody
    @ApiOperation(value = "通过用户id获取菜单")
    public List<MenuIndexDto> getMenu() {
        JwtUserDto jwtUserDto = (JwtUserDto)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Integer userId = jwtUserDto.getMyUser().getId();
        return menuService.getMenu(userId);
    }
在将前端写死的userId删除。现在我们已经能根据登录用户的不同来自动绘制菜单了。
拥有admin权限的用户
普通权限的用户
十、授权
我们目前只是绘制出了不同权限用户能操作的界面,但是还没有真正的进行权限控制。
之前在七中,我们已经将每个用户所拥有的权限集合放入了GrantedAuthority集合中
在之前打印的用户信息中可以看到 authorities中就是该用户所拥有的权限
SpringSecurity会自动帮我们进行权限控制。而我们要做的就是在需要进行权限控制的方法上添加上权限标识即可。
例如:用户管理的权限标识是user:list
我们只需要在相关的接口上加上@PreAuthorize("hasAnyAuthority('user:list')")即可
	@GetMapping("/index")
    @PreAuthorize("hasAnyAuthority('user:list')")
    public String index(){
        return "system/user/user";
    }
    @GetMapping
    @ResponseBody
    @ApiOperation(value = "用户列表")
    @PreAuthorize("hasAnyAuthority('user:list')")
    public Result<MyUser> userList(PageTableRequest pageTableRequest, UserQueryDto userQueryDto){
        pageTableRequest.countOffset();
        return userService.getAllUsersByPage(pageTableRequest.getOffset(),pageTableRequest.getLimit(),userQueryDto);
    }
现在我们登录普通用户来操作相关接口,发现报错
控制台打印
修改所有接口,在需要权限控制的接口上添加注解
十一、自定义异常处理
虽说现在功能已经实现了,用户虽说不能访问没有权限的功能了,但是异常没有处理。如果点击,如果前端也没有做错误的拦截的话,用户会看到一串的报错信息,这很不友好,并且也会对服务器造成压力。
我们只需要在之前创建的全局异常处理类中捕获上图的异常即可。
	@ExceptionHandler(AccessDeniedException.class)
    public Result handleAuthorizationException(AccessDeniedException e)
    {
        log.error(e.getMessage());
        return Result.error().code(ResultCode.FORBIDDEN).message("没有权限,请联系管理员授权");
 }
重启项目,在前端书写相应规则,就会十分友好

十二、自定义退出登录
其实SpringSecurity默认注册了一个/logout路由,通过这个路由可以注销登录状态,包括Session和remember-me等等。
我们可以直接在SpringSecurityConfig的configure中定义相应规则,类似formlogin。也可以自定义一个LogoutHadnler,具体可以看这篇文章
至此SpringSecurity的一些常用功能已经实现,下一节我们整合jwt实现无状态登录
SpringSecurity权限管理系统实战—五、整合SpringSecurity(下)的更多相关文章
- SpringSecurity权限管理系统实战—六、SpringSecurity整合jwt
		目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ... 
- SpringSecurity权限管理系统实战—四、整合SpringSecurity(上)
		目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ... 
- SpringSecurity权限管理系统实战—一、项目简介和开发环境准备
		目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ... 
- SpringSecurity权限管理系统实战—二、日志、接口文档等实现
		系列目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战 ... 
- SpringSecurity权限管理系统实战—七、处理一些问题
		目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ... 
- SpringSecurity权限管理系统实战—八、AOP 记录用户、异常日志
		目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ... 
- SpringSecurity权限管理系统实战—九、数据权限的配置
		目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ... 
- SpringSecurity权限管理系统实战—三、主要页面及接口实现
		系列目录 前言 后端五分钟,前端半小时.. 每次写js都头疼. 自己写前端是不可能的,这辈子不可能自己写前端的,只能找找别人的模板才能维持的了生存这样子.github,gitee上的模板又多,帮助文档 ... 
- [权限管理系统篇] (五)-Spring security(授权过程分析)
		欢迎关注公众号[Ccww笔记],原创技术文章第一时间推出 前言 权限管理系统的组件分析以及认证过程的往期文章: Spring security (一)架构框架-Component.Service.Fi ... 
随机推荐
- 离线安装 docker
			1.基础环境 操作系统:CentOS 7.8 docker 版本:18.06.1 2.docker 下载 2.1 官方地址 https://download.docker.com/linux/stat ... 
- 第四课 OOP封装继承多态解析,接口抽象类选择 2019-04-21
			父类 xx = new 子类(); xx.method(); 1 普通方法由编译时决定(左边) --- 提高效率 2 虚方法(virtual) 由运行时决定-- -多态,灵活 3 抽象方法由运行时决 ... 
- Java后端总结
			Java后端开发学习路线 编程基础 Java语言 语言基础 基础语法 面向对象 接口 容器 异常 泛型 反射 注解 I/O 图形化(如Swing) JVM 类加载机制 字节码执行机制 jvm内存模型 ... 
- pycharm 退出虚拟环境
			pycharm 内置虚拟环境 venv 如果要退出就直接 deactivate 命令就行 运行的话直接在命令行输python3 文件名 
- 05_MySQL数据库
			学于黑马和传智播客联合做的教学项目 感谢 黑马官网 传智播客官网 微信搜索"艺术行者",关注并回复关键词"软件测试"获取视频和教程资料! b站在线视频 软件测试 ... 
- ORACLE表与表联接的几种方式
			三大表与表联接方式 1.NESTED LOOPS 嵌套循环 2.HASH JOIN 哈希联接 3.SORT MERGE 排序合并联接 1.NESTED LOOPS 嵌套循环 嵌套循环的本质是将外部数 ... 
- LVS-NAT:搭建HTTP及HTTPS负载均衡集群
			目录 LVS-NAT:搭建HTTP及HTTPS负载均衡集群 环境说明: 搭建NAT模式的HTTP负载集群 1. 配置好IP地址信息 2. DR上开启IP转发 3.DR上配置lvs-nat的转发机制 4 ... 
- CentOS部署RabbitMQ
			CentOS版本:CentOS-7-x86_64-DVD-1804 RabbitMQ版本:3.7.24 1. 下载安装包 因为RabbitMQ是erlang语言开发的,所以需要提前安装erlang环境 ... 
- 笨办法学python3练习代码ex19.py
			定义函数的语法: def 函数名(参数) (语句) #函数和变量 #函数里的变量与脚本里的变量是没有联系的. def cheese_and_crackers(cheese_count,boxes_o ... 
- 当asp.net core偶遇docker一(模型验证和Rabbitmq 二)
			上一篇我们说到构建了一个Rabbitmq容器 现在我们说说如何在一个悄悄传输消息到队列 我们现在设计一个Rabbitmq发送消息部分的模块 先设计一个远程发送的接口 public interface ... 
