Spring Security 结合 Jwt 实现无状态登录
首先
什么是有状态
什么是无状态
如何实现无状态
无状态登录的流程:
- 首先客户端发送账户名/密码到服务端进行认证
- 认证通过后,服务端将用户信息加密并且编码成一个 token,返回给客户端
- 以后客户端每次发送请求,都需要携带认证的 token
- 服务端对客户端发送来的 token 进行解密,判断是否有效,并且获取用户登录信息
JWT实例
一,Spring Security 配置
/**
* @program: security01
* @author: Mr-Jies
*
**/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/hello").hasRole("user")
.antMatchers("/admin").hasRole("admin")
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtLoginFilter("/login",authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JwtFilter(),UsernamePasswordAuthenticationFilter.class)
.csrf().disable();
}
//认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password(new BCryptPasswordEncoder().encode("123"))
.roles("admin")
.and()
.withUser("jie")
.password(new BCryptPasswordEncoder().encode("jie"))
.roles("user");
}
}
二,JWT 过滤器配置
1,用户登录的过滤器
/**
* @program: security01
* @author: Mr-Jies
用户登录的过滤器,在用户的登录的过滤器中校验用户是否登录成功,
* 如果登录成功,则生成一个token返回给客户端,登录失败则给前端一个登录失败的提示。
*
**/
public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter {
public JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {
super(defaultFilterProcessesUrl);
setAuthenticationManager(authenticationManager);
}
//自动校验
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
// 从登录参数中提取出用户名密码,然后调用AuthenticationManager.authenticate()方法去进行自动校验。
return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));
}
//校验成功
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authResult) throws IOException, ServletException {
Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
StringBuffer as = new StringBuffer();
//将用户角色遍历然后用一个 , 连接起来,
for (GrantedAuthority authority : authorities) {
as.append(authority.getAuthority()).append(",");
}
// 然后再利用Jwts去生成token,按照代码的顺序,生成过程一共配置了四个参数,
//分别是用户角色、主题、过期时间以及加密算法和密钥,
String jwt = Jwts.builder()
.claim("authorities",as)
.setSubject(authResult.getName())
.setExpiration(new Date(System.currentTimeMillis()+10*60*1000))
.signWith(SignatureAlgorithm.HS512,"wuweijie")
.compact();
// 然后将生成的token写出到客户端
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(new ObjectMapper().writeValueAsString(jwt));
writer.flush();
writer.close();
}
//校验失败
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write("登录失败!");
writer.flush();
writer.close();
}
}
2,校验token的过滤器
/**
* @program: security01
* @author: Mr-Jies
*
* 当其他请求发送来,校验token的过滤器,如果校验成功,就让请求继续执行
**/
public class JwtFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
//首先从请求头中提取出 authorization 字段,这个字段对应的value就是用户的token。
String jwtToken = request.getHeader("authorization");
System.out.println("authorities:"+jwtToken);
//将提取出来的token字符串转换为一个Claims对象,
Claims claims = Jwts.parser()
.setSigningKey("wuweijie")
.parseClaimsJws(jwtToken.replace("Bearer",""))
.getBody();
//再从Claims对象中提取出当前用户名和用户角色,
String username = claims.getSubject();
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get("authorities"));
//创建一个UsernamePasswordAuthenticationToken放到当前的Context中,
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(token);
//然后执行过滤链使请求继续执行下去
filterChain.doFilter(request,servletResponse);
}
}
测试
控制层
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello jwt !";
}
@GetMapping("/admin")
public String admin() {
return "hello admin !";
}
}
开始~
将服务器返回的token数据加入到 hello的请求中 就可以顺利访问 hello 了