1.token生成
在用户登录时,请求/api/authenticate,Controller中注入AuthenticationManager用于验证用户名和密码是否正确,验证通过使用tokenProvider创建token并返回给前端
@PostMapping("/authenticate") @Timed public ResponseEntity<JWTToken> authorize(@Valid @RequestBody LoginVM loginVM) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginVM.getUsername(), loginVM.getPassword()); Authentication authentication = this.authenticationManager.authenticate(authenticationToken); SecurityContextHolder.getContext().setAuthentication(authentication); boolean rememberMe = (loginVM.isRememberMe() == null) ? false : loginVM.isRememberMe(); String jwt = tokenProvider.createToken(authentication, rememberMe); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt); return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK); }
2. token验证
使用JWT认证,会生成如下3个类
![](http://upload-images.jianshu.io/upload_images/8365092-72c299f9c9978e99.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/220/format/webp)
import org.springframework.security.config.annotation.SecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; public class JWTConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { private TokenProvider tokenProvider; public JWTConfigurer(TokenProvider tokenProvider) { this.tokenProvider = tokenProvider; } @Override public void configure(HttpSecurity http) throws Exception { JWTFilter customFilter = new JWTFilter(tokenProvider); http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); } }
import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StringUtils; import org.springframework.web.filter.GenericFilterBean; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * Filters incoming requests and installs a Spring Security principal if a header corresponding to a valid user is * found. * */ public class JWTFilter extends GenericFilterBean { public static final String AUTHORIZATION_HEADER = "Authorization"; public static final String AUTHORIZATION_TOKEN = "access_token"; private TokenProvider tokenProvider; public JWTFilter(TokenProvider tokenProvider) { this.tokenProvider = tokenProvider; } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; String jwt = resolveToken(httpServletRequest); if (StringUtils.hasText(jwt) && this.tokenProvider.validateToken(jwt)) { Authentication authentication = this.tokenProvider.getAuthentication(jwt); SecurityContextHolder.getContext().setAuthentication(authentication); } filterChain.doFilter(servletRequest, servletResponse); } private String resolveToken(HttpServletRequest request){ String bearerToken = request.getHeader(AUTHORIZATION_HEADER); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } String jwt = request.getParameter(AUTHORIZATION_TOKEN); if (StringUtils.hasText(jwt)) { return jwt; } return null; } }
import java.nio.charset.StandardCharsets; import java.security.Key; import java.util.*; import java.util.stream.Collectors; import javax.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import io.github.jhipster.config.JHipsterProperties; import io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; @Component public class TokenProvider { private final Logger log = LoggerFactory.getLogger(TokenProvider.class); private static final String AUTHORITIES_KEY = "auth"; private Key key; private long tokenValidityInMilliseconds; private long tokenValidityInMillisecondsForRememberMe; private final JHipsterProperties jHipsterProperties; public TokenProvider(JHipsterProperties jHipsterProperties) { this.jHipsterProperties = jHipsterProperties; } @PostConstruct public void init() { byte[] keyBytes; String secret = jHipsterProperties.getSecurity().getAuthentication().getJwt().getSecret(); if (!StringUtils.isEmpty(secret)) { log.warn("Warning: the JWT key used is not Base64-encoded. " + "We recommend using the `jhipster.security.authentication.jwt.base64-secret` key for optimum security."); keyBytes = secret.getBytes(StandardCharsets.UTF_8); } else { log.debug("Using a Base64-encoded JWT secret key"); keyBytes = Decoders.BASE64.decode(jHipsterProperties.getSecurity().getAuthentication().getJwt().getBase64Secret()); } this.key = Keys.hmacShaKeyFor(keyBytes); this.tokenValidityInMilliseconds = 1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSeconds(); this.tokenValidityInMillisecondsForRememberMe = 1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt() .getTokenValidityInSecondsForRememberMe(); } public String createToken(Authentication authentication, boolean rememberMe) { String authorities = authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); long now = (new Date()).getTime(); Date validity; if (rememberMe) { validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe); } else { validity = new Date(now + this.tokenValidityInMilliseconds); } return Jwts.builder() .setSubject(authentication.getName()) .claim(AUTHORITIES_KEY, authorities) .signWith(key, SignatureAlgorithm.HS512) .setExpiration(validity) .compact(); } public Authentication getAuthentication(String token) { Claims claims = Jwts.parser() .setSigningKey(key) .parseClaimsJws(token) .getBody(); Collection<? extends GrantedAuthority> authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(",")) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); User principal = new User(claims.getSubject(), "", authorities); return new UsernamePasswordAuthenticationToken(principal, token, authorities); } public boolean validateToken(String authToken) { try { Jwts.parser().setSigningKey(key).parseClaimsJws(authToken); return true; } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { log.info("Invalid JWT signature."); log.trace("Invalid JWT signature trace: {}", e); } catch (ExpiredJwtException e) { log.info("Expired JWT token."); log.trace("Expired JWT token trace: {}", e); } catch (UnsupportedJwtException e) { log.info("Unsupported JWT token."); log.trace("Unsupported JWT token trace: {}", e); } catch (IllegalArgumentException e) { log.info("JWT token compact of handler are invalid."); log.trace("JWT token compact of handler are invalid trace: {}", e); } return false; } }
import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; /** * View Model object for storing a user's credentials. * */ public class LoginVM { @NotNull @Size(min = 1, max = 50) private String username; @NotNull @Size(min = ManagedUserVM.PASSWORD_MIN_LENGTH, max = ManagedUserVM.PASSWORD_MAX_LENGTH) private String password; private Boolean rememberMe; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Boolean isRememberMe() { return rememberMe; } public void setRememberMe(Boolean rememberMe) { this.rememberMe = rememberMe; } @Override public String toString() { return "LoginVM{" + "username='" + username + '\'' + ", rememberMe=" + rememberMe + '}'; } }
其中TokenProvider提供创建token,验证token,获取权限等功能
JWTFilter拦截器对token进行校验和权限获取,并赋给SpringContext
JWTConfigurer中配置了JWTFiter的作用时机在UsernamePasswordAuthenticationFilter之前
如此,在请求到达时,通过JWTFiter解析并验证token,给这个请求赋予权限