spring boot下spring security结合token的使用小结
发表于|更新于
|字数总计:1.4k|阅读时长:7分钟|阅读量:
本文将详细介绍在spring boot环境中使用spring security实现token认证的方法和注意事项。
Maven依赖
<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>
|
Spring Security 核心类
要利用Spring Security把安全的事情做了,最核心的就是继承这个WebSecurityConfigurerAdapter
,根据自己的业务需要重新configure
方法,示例代码如下:
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired private JwtAuthenticationEntryPoint unauthorizedHandler;
@Autowired private UserDetailsService userDetailsService;
@Value("${anonymous.path}") private String[] ANONYMOUS_PATH;
@Autowired public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder .userDetailsService(this.userDetailsService) .passwordEncoder(passwordEncoder()); }
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
@Bean public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception { return new JwtAuthenticationTokenFilter(); }
@Override public void configure(WebSecurity web) throws Exception { if (ANONYMOUS_PATH.length > 0) { web.ignoring().antMatchers(ANONYMOUS_PATH); } }
@Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity .csrf().disable() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() .antMatchers( HttpMethod.GET, "/", "/*.html", "/favicon.ico", "/**/*.html", "/**/*.css", "/layui/**", "/dist/**", "/**/*.js" ).permitAll() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() .antMatchers( "/auth/login", "/swagger-ui.html", "/webjars/**", "/swagger-resources/**", "/*/api-docs", "/configuration/**", "/attachment/**", "/system/encrypt", "/actuator/**", "/h2/**").permitAll() .anyRequest().authenticated();
httpSecurity .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
httpSecurity.headers().cacheControl(); httpSecurity.headers().frameOptions().disable(); }
}
|
configure(HttpSecurity httpSecurity)
里面的注释很全,主要做的事情是配置规则和参数。
configure(WebSecurity web)
的目的是实现自定义的匿名访问授权。
OncePerRequestFilter
上面的配置类中声明了要使用authenticationTokenFilterBean
进行权限验证,下面这个继承OncePerRequestFilter
就是它的具体实现,示例如下:
@SuppressWarnings("SpringJavaAutowiringInspection") @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired private JwtUserDetailsService jwtUserDetailsService;
@Autowired private UserService userService;
private String tokenHeader = "Authorization";
@Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { final String authToken = request.getHeader(this.tokenHeader); if (authToken != null) { String usercode = TokenUtils.getUsercodeFromToken(authToken); logger.info("checking authentication " + usercode); if (usercode != null && SecurityContextHolder.getContext().getAuthentication() == null) { SystemUserModel user = userService.getUserByUsercode(usercode); UserDetails userDetails = jwtUserDetailsService.loadUserByUsername(usercode);
if (TokenUtils.validateToken(authToken, userDetails) && userService.isPermissionApi(user.getId(), request.getRequestURI(), request.getMethod())) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails( request)); logger.info("authenticated user " + usercode + ", setting security context"); SecurityContextHolder.getContext().setAuthentication(authentication); } } } chain.doFilter(request, response); } }
|
doFilterInternal
是核心方法,在这里实现了token的提取和认证
UserDetailsService
UserDetailsService
是Spring Security获取用户信息的核心类,配合使用的model是UserDetails
,用这个类去存放用户信息。
@Service public class JwtUserDetailsService implements UserDetailsService {
private UserService userService;
@Override public UserDetails loadUserByUsername(String usercode) throws UsernameNotFoundException { SystemUserModel user = userService.getUserByUsercode(usercode); if (user == null) { throw new UsernameNotFoundException(String.format("No user found with username '%s'.", usercode)); } else { String role = userService.getRole(usercode).getRolename(); return JwtUserFactory.create(user, role); } }
@Autowired public void setUserService(UserService userService) { this.userService = userService; } }
|
loadUserByUsername
是需要重写的方法,在这里写明如何加载用户
UserDetails
下面是UserDetails的一个示例,要点就是要实现UserDetails里的接口
public class JwtUser implements UserDetails { private final String id; private final String username; private final String password; private final String email; private final Collection<? extends GrantedAuthority> authorities;
public JwtUser( String id, String username, String password, String email, Collection<? extends GrantedAuthority> authorities) { this.id = id; this.username = username; this.password = password; this.email = email; this.authorities = authorities; }
@Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; }
@JsonIgnore public String getId() { return id; }
@JsonIgnore @Override public String getPassword() { return password; }
@Override public String getUsername() { return username; }
@JsonIgnore @Override public boolean isAccountNonExpired() { return true; }
@JsonIgnore @Override public boolean isAccountNonLocked() { return true; }
@JsonIgnore @Override public boolean isCredentialsNonExpired() { return true; }
@JsonIgnore @Override public boolean isEnabled() { return true; } }
|
Token 核心类
token这块的重点就是如何生成token,如何验证token,如何刷新token。这些可以封装成一个工具类。如下:
@Component public class TokenUtils {
public static final String CLAIM_KEY_USERNAME = "sub"; public static final String CLAIM_KEY_CREATED = "crt";
private static String secret;
private static Long expiration;
public static String getUsercodeFromToken(String token) { String usercode; try { final Claims claims = getClaimsFromToken(token); usercode = claims.getSubject(); } catch (Exception e) { usercode = e.toString(); } return usercode; }
public static Date getCreatedDateFromToken(String token) { Date created; try { final Claims claims = getClaimsFromToken(token); created = Objects.equals(null, claims) ? null : new Date((Long) claims.get(CLAIM_KEY_CREATED)); } catch (Exception e) { created = null; } return created; }
public static Date getExpirationDateFromToken(String token) { Date expiration; try { final Claims claims = getClaimsFromToken(token); expiration = claims.getExpiration(); } catch (Exception e) { expiration = null; } return expiration; }
private static Claims getClaimsFromToken(String token) { Claims claims; try { claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } catch (Exception e) { claims = null; } return claims; }
private static Date generateExpirationDate() { return new Date(System.currentTimeMillis() + expiration * 1000); }
private static Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); }
private static Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) { return (lastPasswordReset != null && created.before(lastPasswordReset)); }
public static String generateToken(String usercode) { Map<String, Object> claims = new HashMap<>(); claims.put(CLAIM_KEY_USERNAME, usercode); claims.put(CLAIM_KEY_CREATED, new Date()); return generateToken(claims); }
public static String generateToken(Map<String, Object> claims) { return Jwts.builder() .setClaims(claims) .setExpiration(generateExpirationDate()) .signWith(SignatureAlgorithm.HS512, secret) .compact(); }
public static Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) { final Date created = getCreatedDateFromToken(token); return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset) && !isTokenExpired(token); }
public static String refreshToken(String token) { String refreshedToken; try { final Claims claims = getClaimsFromToken(token); claims.put(CLAIM_KEY_CREATED, new Date()); refreshedToken = generateToken(claims); } catch (Exception e) { refreshedToken = null; } return refreshedToken; }
public static Boolean validateToken(String token,SystemUserModel user) { final String usercode = getUsercodeFromToken(token); return (usercode.equals(user.getUsercode()) && !isTokenExpired(token)); }
public static Boolean validateToken(String token,UserDetails userDetails) { final String usercode = getUsercodeFromToken(token); return (usercode.equals(userDetails.getUsername()) && !isTokenExpired(token)); }
@Value("${jwt.secret}") public void setSecret(String secret) { TokenUtils.secret = secret; }
@Value("${jwt.expiration}") public void setExpiration(Long expiration) { TokenUtils.expiration = expiration; } }
|
END