Spring boot默认User没有包含userId信息,在通过SecurityUtils获取userId会很不方便,下面介绍一个简便的方式可以在获取当前用户信息时,能够包含userId。
JHipster是通过一个实现UserDetailService的接口DomainUserDetailsService类,来对登录信息进行认证。
但是在类UserJWTController中,认证的方法如下:
Authentication authentication = authenticationManagerBuilder.getObject()
.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
在获取Authentication的同时,把它放入SecurityContextHolder的上下文。也就是说,我们需要在authentication中包含userId信息,那么这里的authenticate方法是如何关联DomainUserDetailsService中的loadUserByUsername方法呢?
JHipster并没有做任何处理,只是利用了spring boot的默认配置,我们来看一下security部分的autoconfiguration。
在org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration类中,initializeUserDetailsBeanManagerConfigurer方法创建了一个InitializeUserDetailsBeanManagerConfigurer类,该类的configure(AuthenticationManagerBuilder auth)方法将UserDetailsService赋给了DaoAuthenticationProvider。
class InitializeUserDetailsManagerConfigurer
extends GlobalAuthenticationConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (auth.isConfigured()) {
return;
}
UserDetailsService userDetailsService = getBeanOrNull(
UserDetailsService.class);
if (userDetailsService == null) {
return;
}
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
if (passwordEncoder != null) {
provider.setPasswordEncoder(passwordEncoder);
}
if (passwordManager != null) {
provider.setUserDetailsPasswordService(passwordManager);
}
provider.afterPropertiesSet();
auth.authenticationProvider(provider);
}
这里我们只要记住DaoAuthenticationProvider包含了userDetailsService。
下面再找到authenticationManager和DaoAuthenticationProvider的关系:Provider可以看成等同于Manager类,它多了一个是否支持authentication的support方法,这样authenticationManager的authenticate方法和loadUserByUsername方法就关联到一起。
为了使authentication类中包含userId信息,
第一步,扩展org.springframework.security.core.userdetails.User类
public class AuthenticatedUser extends User {
private String userId = "";
public AuthenticatedUser(String userId, String username,
String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
this.userId = userId;
}
public String getUserId() {
return this.userId;
}
}
第二步,改变loadUserByUsername返回AuthenticatedUser类
private AuthenticatedUser createSpringSecurityUser(ApplicationUser user) {
List<GrantedAuthority> grantedAuthorities =
getAuthorities(user.toApplicationUserDto());
return new AuthenticatedUser(user.getId(),
user.getUserName(),user.getPassword(), grantedAuthorities);
}
第三步,在TokenProvider中改变createToken和getAuthentication两个方法,添加userId信息
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)
.claim("userId", ((AuthenticatedUser)authentication.getPrincipal()).getUserId())
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validity)
.compact();
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parserBuilder().setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
AuthenticatedUser principal = new AuthenticatedUser(claims.get("userId").toString(), claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
本文介绍是基于跟着JHipster学做项目 (6) 安全访问控制(上)JWT的Spring Boot应用
Good Luck,
Cheers!