权限控制是非常常见的功能,在各种后台管理里权限控制更是重中之重。在 Spring Boot 中使用 Spring Security 构建权限系统是非常轻松和简单的。Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
 
添加依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-security</artifactId >  </dependency > <dependency >     <groupId > io.jsonwebtoken</groupId >      <artifactId > jjwt</artifactId >      <version > 0.9.0</version >  </dependency > <dependency >     <groupId > org.springframework.security</groupId >      <artifactId > spring-security-test</artifactId >      <scope > test</scope >  </dependency > 
 
UserDetails 按照官方文档的说法,为了定义我们自己的认证管理,我们可以添加 UserDetailsService, AuthenticationProvider, AuthenticationManager 这种类型的 Bean。实现的方式有多种,这里我选择最简单的一种(因为本身我们这里的认证授权也比较简单)通过定义自己的 UserDetailsService 从数据库查询用户信息,至于认证的话就用默认的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 public  class  User  implements  UserDetails  {    private  Integer id;     private  String username;     private  String password;     private  Date lastPasswordResetDate;     private  List<Role> roles = new  ArrayList <>();     public  Integer getId ()  {         return  id;     }     public  void  setId (Integer id)  {         this .id = id;     }     public  void  setUsername (String username)  {         this .username = username;     }     public  void  setPassword (String password)  {         this .password = password;     }     @JsonIgnore      public  List<Role> getRoles ()  {         return  roles;     }     public  void  setRoles (List<Role> roles)  {         this .roles = roles;     }     @Override      @JsonIgnore      public  Collection<? extends  GrantedAuthority > getAuthorities() {         return  roles.stream().map(Role::getName).map(SimpleGrantedAuthority::new ).collect(Collectors.toList());     }     @Override      public  String getPassword ()  {         return  password;     }     @Override      public  String getUsername ()  {         return  username;     }     @Override      @JsonIgnore      public  boolean  isAccountNonExpired ()  {         return  true ;     }     @Override      @JsonIgnore      public  boolean  isAccountNonLocked ()  {         return  true ;     }     @Override      @JsonIgnore      public  boolean  isCredentialsNonExpired ()  {         return  true ;     }     @Override      @JsonIgnore      public  boolean  isEnabled ()  {         return  true ;     }     @JsonIgnore      public  Date getLastPasswordResetDate ()  {         return  lastPasswordResetDate;     }     public  void  setLastPasswordResetDate (Date lastPasswordResetDate)  {         this .lastPasswordResetDate = lastPasswordResetDate;     } } 
 
开启权限验证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public  class  WebSecurityConfig  extends  WebSecurityConfigurerAdapter  {         @Resource      private  UserDetailsService userDetailsService;     @Resource      private  AuthenticationTokenFilter authenticationTokenFilter;          @Override      public  void  configure (AuthenticationManagerBuilder authenticationManagerBuilder)  throws  Exception {         authenticationManagerBuilder.userDetailsService(this .userDetailsService).passwordEncoder(passwordEncoder());     }     @Override      protected  void  configure (HttpSecurity httpSecurity)  throws  Exception {                  httpSecurity.csrf().disable()                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()                 .authorizeRequests()                                  .antMatchers("/authentication/**" ).permitAll()                                  .anyRequest().authenticated();         httpSecurity.headers().cacheControl();                  httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);     }     @Bean      @Override      public  AuthenticationManager authenticationManagerBean ()  throws  Exception {         return  super .authenticationManagerBean();     }     @Bean      public  PasswordEncoder passwordEncoder ()  {         return  new  BCryptPasswordEncoder ();     } } 
 
集成 JWT 想要将 JWT(Json Web Token) 在 spring boot 中集成起来,需要创建一个 filter,继承自 OncePerRequestFilter。 并且将它添加到 WebSecurityConfig,需要注意的是,这个过滤器是作为权限验证,必须添加到 before 集合中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Component public  class  AuthenticationTokenFilter  extends  OncePerRequestFilter  {    @Resource      private  UserDetailsService userDetailsService;     @Resource      private  TokenUtil tokenUtil;     @Override      protected  void  doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)  throws  ServletException, IOException {         String  authorization  =  request.getHeader(AUTHORIZATION);         if  (!StringUtils.isEmpty(authorization) && authorization.startsWith(TokenUtil.TOKEN_PREFIX)) {             String  token  =  authorization.substring(TokenUtil.TOKEN_PREFIX.length());             String  username  =  tokenUtil.getUsernameFromToken(token);             if  (!StringUtils.isEmpty(username) && SecurityContextHolder.getContext().getAuthentication() == null ) {                 UserDetails  userDetails  =  this .userDetailsService.loadUserByUsername(username);                 if  (tokenUtil.validateToken(token, userDetails)) {                     UsernamePasswordAuthenticationToken  authentication  =  new  UsernamePasswordAuthenticationToken (                             userDetails, null , userDetails.getAuthorities()                     );                     authentication.setDetails(new  WebAuthenticationDetailsSource ().buildDetails(request));                     SecurityContextHolder.getContext().setAuthentication(authentication);                 }             }         }         filterChain.doFilter(request, response);     } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 @Component public  class  TokenUtil  implements  Serializable  {    public  static  final  String  TOKEN_PREFIX  =  "Bearer " ;     static  final  String  CLAIM_KEY_USERNAME  =  "sub" ;     static  final  String  CLAIM_KEY_CREATED  =  "iat" ;     private  static  final  long  serialVersionUID  =  -3301605591108950415L ;     private  Clock  clock  =  DefaultClock.INSTANCE;     @Value("${jwt.secret}")      private  String secret;     @Value("${jwt.expiration}")      private  Long expiration;     public  String getUsernameFromToken (String token)  {         return  getClaimFromToken(token, Claims::getSubject);     }     public  Date getIssuedAtDateFromToken (String token)  {         return  getClaimFromToken(token, Claims::getIssuedAt);     }     public  Date getExpirationDateFromToken (String token)  {         return  getClaimFromToken(token, Claims::getExpiration);     }     public  <T> T getClaimFromToken (String token, Function<Claims, T> claimsResolver)  {         final  Claims  claims  =  getAllClaimsFromToken(token);         return  claimsResolver.apply(claims);     }     private  Claims getAllClaimsFromToken (String token)  {         return  Jwts.parser()                 .setSigningKey(secret)                 .parseClaimsJws(token)                 .getBody();     }     private  Boolean isTokenExpired (String token)  {         final  Date  expiration  =  getExpirationDateFromToken(token);         return  expiration.before(clock.now());     }     private  Boolean isCreatedBeforeLastPasswordReset (Date created, Date lastPasswordReset)  {         return  (lastPasswordReset != null  && created.before(lastPasswordReset));     }     private  Boolean ignoreTokenExpiration (String token)  {         return  false ;     }     public  String generateToken (UserDetails userDetails)  {         Map<String, Object> claims = new  HashMap <>();         return  doGenerateToken(claims, userDetails.getUsername());     }     private  String doGenerateToken (Map<String, Object> claims, String subject)  {         final  Date  createdDate  =  clock.now();         final  Date  expirationDate  =  calculateExpirationDate(createdDate);         return  Jwts.builder()                 .setClaims(claims)                 .setSubject(subject)                 .setIssuedAt(createdDate)                 .setExpiration(expirationDate)                 .signWith(SignatureAlgorithm.HS512, secret)                 .compact();     }     public  Boolean canTokenBeRefreshed (String token, Date lastPasswordReset)  {         final  Date  created  =  getIssuedAtDateFromToken(token);         return  !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)                 && (!isTokenExpired(token) || ignoreTokenExpiration(token));     }     public  String refreshToken (String token)  {         final  Date  createdDate  =  clock.now();         final  Date  expirationDate  =  calculateExpirationDate(createdDate);         final  Claims  claims  =  getAllClaimsFromToken(token);         claims.setIssuedAt(createdDate);         claims.setExpiration(expirationDate);         return  Jwts.builder()                 .setClaims(claims)                 .signWith(SignatureAlgorithm.HS512, secret)                 .compact();     }     public  Boolean validateToken (String token, UserDetails userDetails)  {         if  (userDetails instanceof  User) {             User  user  =  (User) userDetails;             final  String  username  =  getUsernameFromToken(token);             final  Date  created  =  getIssuedAtDateFromToken(token);             return  (                     username.equals(user.getUsername())                             && !isTokenExpired(token)                             && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())             );         } else  {             System.out.println("类型转换失败!" );             return  false ;         }     }     private  Date calculateExpirationDate (Date createdDate)  {         return  new  Date (createdDate.getTime() + expiration * 1000 );     } } 
 
UserDetailsService 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Service public  class  UserDetailsServiceImpl  implements  UserDetailsService  {    @Resource      private  UserMapper userMapper;     @Resource      private  RoleMapper roleMapper;     @Override      public  UserDetails loadUserByUsername (String username)  throws  UsernameNotFoundException {         User  user  =  userMapper.findByUsername(username);         if  (user == null )             throw  new  UsernameNotFoundException ("Cannot find user with username, username = "  + username);         user.setRoles(roleMapper.selectByUserId(user.getId()));         return  user;     } }