这里我们已账户密码登录为例
public ResponseEntity<MsgInfo<UserVM>> authenticate(@Valid @RequestBody LoginVM loginVM) {
Map<String, String> objects = new HashMap<>();
objects.put("password", loginVM.getPassword());
final UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginVM.getUsername(),
objects);
final Authentication authentication = this.authenticationManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
final boolean rememberMe = (loginVM.getRememberMe() == null) ? false : loginVM.getRememberMe();
final String jwt = tokenProvider.createToken(authentication, rememberMe);
final UserVM user = userService.getUserWithAuthoritiesByLogin(loginVM.getUsername());
if (!loginStatus.equals(user.getStatus())) {
throw new RuntimeException("您的账号已被冻结,暂时不能登录.");
}
final HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " \+ jwt);
userLoginLogService.insertOne(user);
return new ResponseEntity<>(new MsgInfo(StatusCode.ok, user), httpHeaders, HttpStatus.OK);
}
一、继承org.springframework.security.authentication.AbstractAuthenticationToken,重写部分方法
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL\_VERSION\_UID;
private Object principal;
private Map<String, String> credentials;
private Map<String, String> details;
@Override
public Object getDetails() {
return this.details;
}
public UsernamePasswordAuthenticationToken(Object principal, Map<String, String> credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
public UsernamePasswordAuthenticationToken(Object principal,
Map<String, String> credentials,
Collection<? extends GrantedAuthority> authorities,
Map<String,String> details) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
this.details=details;
super.setAuthenticated(true); // must use super, as we override
}
public UsernamePasswordAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = null;
super.setAuthenticated(true); // must use super, as we override
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
credentials = null;
}
// public MobileCodeAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {
// super(authorities);
// this.principal = principal;
// this.credentials = null;
// super.setAuthenticated(true); // must use super, as we override
// }
@Override
public Object getCredentials() {
// TODO Auto-generated method stub
return this.credentials;
}
@Override
public Object getPrincipal() {
// TODO Auto-generated method stub
return this.principal;
}
}
这里相当于一个缓存区域,可以缓存用户信息,其中details是很重要的一个变量。在授权过程中其他信息会被清除,但是details属性回保留下来。
二、实现org.springframework.security.authentication.AuthenticationProvider,编写一个第一步写的token的授权提供者
@Component
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserService userService;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthorityMapper authorityMapper;
@Override
@SuppressWarnings("unchecked")
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
Map<String, String> params = (Map<String, String>) authentication.getCredentials();
String password = params.get("password");
if (StringUtils.isEmpty(password)) {
throw new BadCredentialsException("密码不能为空");
}
User user = userService.getUserByAccount(username);
if (null == user) {
throw new BadCredentialsException("用户不存在");
}
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("用户名或密码不正确");
}
Map<String, String> details = new HashMap<>(4);
details.put("loginType", "password");
details.put("login", user.getLogin());
details.put("telephone", user.getTelephone());
details.put("email", user.getEmail());
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
username,
params,
listUserGrantedAuthorities(user.getLogin()),
details);
result.setDetails(authentication.getDetails());
return result;
}
@Override
public boolean supports(Class<?> authentication) {
System.out.println(this.getClass().getName() + "---supports");
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
private Set<GrantedAuthority> listUserGrantedAuthorities(String login) {
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
if (StringUtils.isEmpty(login)) {
return authorities;
}
Set<Authority> authority = authorityMapper.selectByUserLogin(login);
for (Authority auth : authority) {
authorities.add(new SimpleGrantedAuthority(auth.getName()));
}
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return authorities;
}
}
public Authentication authenticate(Authentication authentication)这个方法是springSecurity框架在校验授权的过程中会去调用校验的,经过这个authentication()方法之后就是已经经过了校验了。
public boolean supports(Class<?> authentication) 这个方法需要你把第一步的token和该provider进行匹配。
三、颁发jwt令牌
public String createToken(Authentication authentication, boolean rememberMe) {
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
return this.createToken(authentication.getName(), authentication.getDetails(), authorities, rememberMe);
}
@SuppressWarnings("unchecked")
public String createToken(String loginName, Object details, String authorities, boolean rememberMe) {
long now = (new Date()).getTime();
Date validity;
if (rememberMe) {
validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe \+ this.tokenValidityInMilliseconds);
} else {
validity = new Date(now + this.tokenValidityInMilliseconds \* 2);
}
Map<String, String> params;
if (details != null) {
params = (Map<String, String>) details;
} else {
params = new HashMap<>(0);
}
return Jwts.builder()
.setSubject(loginName)
.claim(AUTHORITIES_KEY, authorities)
.claim("telephone", params.get("telephone"))
.claim("email", params.get("email"))
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validity)
.compact();
}
这是一个示范,header里面是加密的方式,payload是其中的一些信息。 所以自定义的一些需要存储到token里面的可以用claim(key,value)来加入其中。
这里稍后博客会说明jwt令牌的构成。
四、客户带着token访问时,在过滤器(Filter)中编写过滤器(分布式就要用zuul网关)
继承org.springframework.web.filter.GenericFilterBean,实现过滤器
public class JWTFilter extends GenericFilterBean {
public static final String AUTHORIZATION_HEADER = "Authorization";
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, bearerToken.length());
}
return null;
}
}
其中包含解析token的,确认token是不是系统办法的
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;
}
直接解析token来进行获取到自己编写的token类
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
//在Token过期时间到期后,并在另一个ExpireationTime时间范围内,系统会自动刷新Token,并返回前端
long now = new Date().getTime() - ((long) claims.getExpiration().getTime() - this.tokenValidityInMilliseconds);
boolean refreshed = false;
if (0 < now && now < this.tokenValidityInMilliseconds) {
token = refreshToken(claims);
refreshed = true;
}
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
Map<String, String> properties = new HashMap<>(3);
properties.put("telephone", String.valueOf(claims.get("telephone")));
properties.put("email", String.valueOf(claims.get("email")));
AuthenticationUser principal = new AuthenticationUser(claims.getSubject(), "", authorities, properties);
CommonAuthenticationToken authenticationToken = new CommonAuthenticationToken(
principal,
token,
authorities);
authenticationToken.setDetails(refreshed);
return authenticationToken;
}
SecurityContextHolder.getContext().setAuthentication(authentication); 就是将该登录客户放入缓存中。
五、进行springSecurity配置适配器进行
写一个继承自org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter的类
@Configuration
@EnableWebSecurity //springSecurity自带注解
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) //使用该配置进行
@Import(SecurityProblemSupport.class)
@Order(1000) //放到后面再加载
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final UserDetailsService userDetailsService;
private final TokenProvider tokenProvider;
private final CorsFilter corsFilter;
private final SecurityProblemSupport problemSupport;
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Autowired //用户名和密码登录
private UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider;
@Autowired //验证码登录
private MobileCodeAuthenticationProvider mobileCodeAuthenticationProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth //这里是将我们写好的provider注入到springSecurity框架中
//该地放provider的顺序是框架springSecurity调用authentication()的顺序
//只要有一个provider就可以通过就可以,全部不通过就算是全部失败,验证失败
.authenticationProvider(mobileCodeAuthenticationProvider)
.authenticationProvider(usernamePasswordAuthenticationProvider)
;
}
public SecurityConfiguration(AuthenticationManagerBuilder authenticationManagerBuilder, UserDetailsService userDetailsService, TokenProvider tokenProvider, CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
this.authenticationManagerBuilder = authenticationManagerBuilder;
this.userDetailsService = userDetailsService;
this.tokenProvider = tokenProvider;
this.corsFilter = corsFilter;
this.problemSupport = problemSupport;
}
@PostConstruct
public void init() {
try {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setHideUserNotFoundExceptions(false);
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
authenticationManagerBuilder
.authenticationProvider(authenticationProvider);
} catch (Exception e) {
throw new BeanInitializationException("Security configuration failed", e);
}
}
@Override
@Bean public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager authenticationManager = super.authenticationManagerBean();
return authenticationManager;
}
@Bean //这个是密码加密器,不可逆转
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**");
}
@Override
public void configure(HttpSecurity http1) throws Exception {
ExceptionHandlingConfigurer<HttpSecurity> http = http1
.csrf()
.disable()
.addFilterBefore(bhAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport);
http.and()
.headers()
.frameOptions()
.disable();
http.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.and()
.authorizeRequests()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/account/reset-password/init").permitAll()
.antMatchers("/api/account/reset-password/finish").permitAll()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/info").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN);
http.and()
.authorizeRequests()
.antMatchers("/api/**").permitAll(); //关闭权限验证
//.antMatchers("/api/**").authenticated(); //打开权限验证 http.and()
.apply(securityConfigurerAdapter());
}
@Bean
public BhAuthenticationFilter bhAuthenticationFilter() {
BhAuthenticationFilter filter = new BhAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager);
return filter;
}
private JWTConfigurer securityConfigurerAdapter() {
return new JWTConfigurer(tokenProvider);
}
}
到此,springSecurity已经完成jwt令牌的颁发和校验。
来源:oschina
链接:https://my.oschina.net/edisonOnCall/blog/3160145