JWT
JWT(JSON Web Token),这种方式服务器端就不需要保存Session数据了,只用在客户端保存服务端返回给客户的Token就可以了,扩展性得到提升。JWT 本质上就一段签名的JSON格式的数据。由于它是带有签名的,因此接收者便可以验证它的真实性。 详情请看:认证授权这一篇文章
引入依赖
jjwt依赖封装了JWT一些常用的工具,用于生成JWT和解析JWT
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
创建用户对象实现UserDetails接口
UserDetails是Spring Security提供的一个接口,可以定义一些登录用户的属性
@Setter
@Getter
@ToString
public class User implements UserDetails {
private String username;
private String password;
private List<GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
定义测试接口
引入Spring Security依赖后,默认所有接口是封闭的,必须登录后才能访问,这时候就需要一些过滤器做一些校验规则。
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello, jwt!";
}
@GetMapping("/admin")
public String admin() {
return "hello, admin!";
}
}
用户登录的过滤器
这个过滤器主要用于用户登录成功后,生成JWT返回前端,失败后显示错误提示。只会拦截defaultFilterProcessesUrl
public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter {
/**
*
* @param defaultFilterProcessesUrl 默认拦截的URL
* @param authenticationManager
*/
protected JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {
super(defaultFilterProcessesUrl);
setAuthenticationManager(authenticationManager);
}
/**
* 验证账号密码是否正确
*
* @param httpServletRequest
* @param httpServletResponse
* @return
* @throws AuthenticationException
* @throws IOException
* @throws ServletException
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
User user = new ObjectMapper().readValue(httpServletRequest.getInputStream(), User.class);
Authentication authenticate = getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));
return authenticate;
}
/**
* 验证成功
*
* @param request
* @param response
* @param chain
* @param authResult
* @throws IOException
* @throws ServletException
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
StringBuffer stringBuffer = new StringBuffer();
for (GrantedAuthority authority : authorities) {
stringBuffer.append(authority.getAuthority()).append(",");
}
// 生成JWT,过期时间为1小时
String jwt = Jwts.builder().claim("authorities", stringBuffer.toString())
.setSubject(authResult.getName())
.setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 1000))
.signWith(SignatureAlgorithm.HS512, "tong@123")
.compact();
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(new ObjectMapper().writeValueAsString(jwt));
writer.flush();
writer.close();
}
/**
* 验证失败
*
* @param request
* @param response
* @param failed
* @throws IOException
* @throws ServletException
*/
@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();
}
}
校验token的过滤器
用户访问接口的时候,校验接口是否携带token,以及token是否正确,如果正确就放行,如果不正确返回错误
public class JwtFilter extends GenericFilterBean {
/**
* 校验token是否正确
*
* @param servletRequest
* @param servletResponse
* @param filterChain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String token = request.getHeader("authorization");
if (StringUtils.isEmpty(token)) {
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write("token不能空");
writer.flush();
writer.close();
}
try {
Claims claims = Jwts.parser().setSigningKey("tong@123").parseClaimsJws(token.replace("Bearer", "")).getBody();
String username = claims.getSubject();
List<GrantedAuthority> authorization = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get("authorities"));
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, authorization);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
} catch (ExpiredJwtException e) {
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write("token失效");
writer.flush();
writer.close();
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
Spring Security配置
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* PasswordEncoder密码加密, 这里使用的是不加密,推荐使用BCryptPasswordEncoder加密
*
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
/**
* 在内存里生生成一些用户
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin")
.password("123")
.roles("admin")
.and()
.withUser("tong")
.password("456")
.roles("user");
}
/**
* /hello 接口必须要具备 user 角色才能访问,
* /admin 接口必须要具备 admin 角色才能访问,
* POST 请求并且是 /login 接口则可以直接通过,
* 其他接口必须认证后才能访问
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/hello").hasRole("user")
.antMatchers("/admin").hasRole("admin")
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JwtLoginFilter("/login", authenticationManager()), JwtFilter.class)
.csrf().disable();
}
}
访问
-
不登陆直接访问hello接口,直接报错token不能为空
-
输入错误密码,直接报登录失败
-
输入正确的用户密码,登录成功并返回token
-
访问hello接口,返回成功