关于JWT,之前的文章详细介绍,本篇我们将介绍JWT在Spring微服务中的应用。
工作流程
首先,客户端通过用户名和密码发起请求到开放接口,服务端收到请求之后,对用户名和密码进行验证,验证通过之后,生成JWT并响应给客户端。客户端收到服务器响应之后,将JWT保存到本地,后续客户端的所有请求的HTTP Header都将包含此Token信息,进而服务器端可以对所有请求进行授权验证。
JWT的生成与验证
public class JsonWebTokenUtility {
private SignatureAlgorithm signatureAlgorithm;
private Key secretKey;
public JsonWebTokenUtility() {
// THIS IS NOT A SECURE PRACTICE!
// For simplicity, we are storing a static key here.
// Ideally, in a microservices environment, this key would kept on a
// config server.
signatureAlgorithm = SignatureAlgorithm.HS512;
String encodedKey =
"L7A/6zARSkK1j7Vd5SDD9pSSqZlqF7mAhiOgRbgv9Smce6tf4cJnvKOjtKPxNNnWQj+2lQEScm3XIUjhW+YVZg==";
secretKey = deserializeKey(encodedKey);
}
public String createJsonWebToken(AuthTokenDetailsDTO authTokenDetailsDTO) {
String token = Jwts.builder().setSubject(authTokenDetailsDTO.userId).claim
("email", authTokenDetailsDTO.email)
.claim("roles", authTokenDetailsDTO.roleNames).setExpiration
(authTokenDetailsDTO.expirationDate)
.signWith(getSignatureAlgorithm(), getSecretKey()).compact();
return token;
}
private Key deserializeKey(String encodedKey) {
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
Key key = new SecretKeySpec(decodedKey, getSignatureAlgorithm().getJcaName());
return key;
}
private Key getSecretKey() {
return secretKey;
}
public SignatureAlgorithm getSignatureAlgorithm() {
return signatureAlgorithm;
}
public AuthTokenDetailsDTO parseAndValidate(String token) {
AuthTokenDetailsDTO authTokenDetailsDTO = null;
try {
Claims claims = Jwts.parser().setSigningKey
(getSecretKey()).parseClaimsJws(token).getBody();
String userId = claims.getSubject();
String email = (String) claims.get("email");
List roleNames = (List) claims.get("roles");
Date expirationDate = claims.getExpiration();
authTokenDetailsDTO = new AuthTokenDetailsDTO();
authTokenDetailsDTO.userId = userId;
authTokenDetailsDTO.email = email;
authTokenDetailsDTO.roleNames = roleNames;
authTokenDetailsDTO.expirationDate = expirationDate;
} catch (JwtException ex) {
System.out.println(ex);
}
return authTokenDetailsDTO;
}
private String serializeKey(Key key) {
String encodedKey = Base64.getEncoder().encodeToString(key.getEncoded());
return encodedKey;
}
}
微服务中的Spring Security配置
Spring Security中权限处理由AuthenticationManager组件完成,我们需要在AuthenticationManager注册一个自定义拦截器JsonWebTokenAuthenticationFilter拦截请求(JsonWebTokenAuthenticationFilter继承RequestHeaderAuthenticationFilter),JsonWebTokenAuthenticationFilter负责读取请求报头中是否包含Authorization信息:
public class JsonWebTokenAuthenticationFilter extends RequestHeaderAuthenticationFilter {
public JsonWebTokenAuthenticationFilter() {
// Don't throw exceptions if the header is missing
this.setExceptionIfHeaderMissing(false);
// This is the request header it will look for
this.setPrincipalRequestHeader("Authorization");
}
@Override
@Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
}
在这一步中,请求报头将以PreAuthenticatedAuthenticationToken的形式转换为Spring Authentication对象,拦截器读取转换完之后,我们需要在Authentication Manager中注册Provider来读取、解析,并将其转换为自定义验签对象,Provider实现如下:
public class JsonWebTokenAuthenticationProvider implements AuthenticationProvider {
private JsonWebTokenUtility tokenService = new JsonWebTokenUtility();
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Authentication authenticatedUser = null;
// Only process the PreAuthenticatedAuthenticationToken
if (authentication.getClass().isAssignableFrom(PreAuthenticatedAuthenticationToken.class)
&& authentication.getPrincipal() != null) {
String tokenHeader = (String) authentication.getPrincipal();
UserDetails userDetails = parseToken(tokenHeader);
if (userDetails != null) {
authenticatedUser = new JsonWebTokenAuthentication(userDetails, tokenHeader);
}
} else {
// It is already a JsonWebTokenAuthentication
authenticatedUser = authentication;
}
return authenticatedUser;
}
private UserDetails parseToken(String tokenHeader) {
UserDetails principal = null;
AuthTokenDetailsDTO authTokenDetails = tokenService.parseAndValidate(tokenHeader);
if (authTokenDetails != null) {
List authorities = authTokenDetails.roleNames.stream()
.map(roleName -> new SimpleGrantedAuthority(roleName)).collect(Collectors.toList());
principal = new User(authTokenDetails.email, "", authorities);
}
return principal;
}
@Override
public boolean supports(Class> authentication) {
return authentication.isAssignableFrom(PreAuthenticatedAuthenticationToken.class)
|| authentication.isAssignableFrom(JsonWebTokenAuthentication.class);
}
}
Spring Security Java Configuration
配置Spring Security Java Configuration,注册Authentication Manager Bean
public abstract class JsonWebTokenSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JsonWebTokenAuthenticationProvider authenticationProvider;
@Autowired
private JsonWebTokenAuthenticationFilter jsonWebTokenFilter;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
// This method is here with the @Bean annotation so that Spring can
// autowire it
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// disable CSRF, http basic, form login
.csrf().disable() //
.httpBasic().disable() //
.formLogin().disable()
// ReST is stateless, no sessions
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //
.and()
// return 403 when not authenticated
.exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint());
// Let child classes set up authorization paths
setupAuthorization(http);
http.addFilterBefore(jsonWebTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
protected abstract void setupAuthorization(HttpSecurity http) throws Exception;
}
定义与调用
微服务与微服务之间的接口调用,我们可以使用@FeignClient来进行调用,比如,端到端的服务调用定义:
@FeignClient("user-management-service")
public interface UserManagementServiceAPI {
@RequestMapping(method = RequestMethod.POST, value = "/roles")
RoleDTO createRole(@RequestHeader("Authorization")
String authorizationToken, @RequestBody RoleDTO roleDTO);
}
@RequestHeader(“Authorization”) String authorizationToken
服务之间端到端的服务调用可以通过下面这种方式来获取Token
private String getAuthorizationToken() {
String token = null;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null &&
authentication.getClass().isAssignableFrom(JsonWebTokenAuthentication.class)) {
JsonWebTokenAuthentication jwtAuthentication = (JsonWebTokenAuthentication) authentication;
token = jwtAuthentication.getJsonWebToken();
}
return token;
}
客户端请求可以通过设置ajax的headers参数的Authorization请求参数
$.ajax({
url: '',
headers:{Authorization:'token'},
...
})