SpringSecurity权限管理系统实战—1、项目简介和开发环境准备
SpringSecurity权限管理系统实战—2、日志、接口文档等实现
SpringSecurity权限管理系统实战—3、主要页面及接口实现
SpringSecurity权限管理系统实战—4、整合SpringSecurity(上)
SpringSecurity权限管理系统实战—5、整合SpringSecurity(下)
SpringSecurity权限管理系统实战—6、SpringSecurity整合jwt
SpringSecurity权限管理系统实战—7、处理一些问题
SpringSecurity权限管理系统实战—8、AOP 记录用户日志、异常日志html
上篇文章SpringSecurity整合了一半,此次把另外一半整完,因此本篇的序号接着上一篇。前端
前面咱们登陆都是用的指定的用户名和密码或者是springsecurity默认的用户名和打印出来的密码。咱们要想链接上自定义数据库只须要实现一个自定义的UserDetailsService。java
咱们新建一个JwtUserDto继承UserDetails并实现它的方法git
@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实现UserDetailsServicegithub
@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。若是修改了又删除掉了,它就会是空值。spring
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的事就要被拿出来遛一遛。session
那么为何密码加密怎么重要??由于在你的数据库被攻击泄露了数据时,若是你的密码也被黑客掌握,那么即便你修复好了数据库泄露的问题,黑客手上仍然还有着用户的密码(总不能要求全部用户修改密码吧)
因此咱们须要在系统开发之初就尽可能的避免这种问题。
那么说了这么多,怎么来加密呢?
其实在SpringSecurity种已经内置了密码的加密机制,只须要实现一个PasswordEncoder接口便可。
来看一下源码
public interface PasswordEncoder { String encode(CharSequence var1); boolean matches(CharSequence var1, String var2); default boolean upgradeEncoding(String encodedPassword) { return 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实现无状态登陆