1. Jwt 简介
JSON Web Token (JWT)是一种基于 token 的认证方案。
随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。
2. Jwt 的组成
JSON Web Tokens 由用点 ( .)分隔的三个部分组成,
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSAiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMn0. SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
分别是:
- 标题 (header)
- 载荷 (payload)
- 签名 (signature)
-
- 标题为第一部分,通常由两部分组成:令牌的类型,即 JWT,以及正在使用的签名算法
-
- 载荷是第二部分,其中包含声明。声明是关于实体(通常是用户)和附加数据的声明。 共有三种类型的声 明:注册声明、公共声明和私有声明。
-
- 签名为第三部分,要创建签名部分,您必须获取编码的标头、编码的有效载荷、秘密,标头中指定的算法,并对其进行签名。
以 HMACSHA256为签名算法 为例简化为下面结构:
`
base64UrlEncode(header).
base64UrlEncode(payload).
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
) `
3. Jwt 的使用——JJWT
jwt的框架:JJWT 其实就是简单创建使用jwt
JJWT是一个提供端到端的JWT创建和验证的Java库。 JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。
3.1 导入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
3.2 创建JwtUtil 工具类
通过 JJwt 中的 Jwts,来创建解析 jwt
@Data
@Component
@ConfigurationProperties(prefix = "ljw.jwt") //通过配置文件设置参数
public class JwtUtil {
private long expire;
private String secret;
private String header; // jwt 的名称
// 生成jwt
public String generateToken(String username){
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime()+1000*expire);
String jwt = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(username) // 主体
.setIssuedAt(nowDate) // 创建时间
.setExpiration(expireDate) // 过期时间
.signWith(SignatureAlgorithm.HS256, secret) // 指定签名生成算法,及密钥
.compact();
return jwt;
}
// 解析jwt
public Claims getClaimsToken(String jwt){
// 返回结果若不为空,则jwt 合法, 否则不合法
try {
return Jwts.parser()
.setSigningKey(secret) // 通过密钥解析
.parseClaimsJws(jwt)
.getBody();
} catch (Exception e) {
return null;
}
}
// jwt 是否过期
public boolean isTokenExpired(Claims claims){
return claims.getExpiration().before(new Date());
}
}
application.yml 配置文件中
ljw:
jwt:
header: Authorization
expire: 604800 #7天,秒单位
secret: ji8n3439n439n43ld9ne9343fdfer49h
3.3 结合Spring Sevurity 使用
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
JwtUtil jwtUtil;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream outputStream = response.getOutputStream();
// 生成jwt 放入 请求头 header中
String token = jwtUtil.generateToken(authentication.getName());
response.setHeader(jwtUtil.getHeader(),token);
Result result = Result.success("");
outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
outputStream.flush();
outputStream.close();
}
}
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
@Autowired
JwtUtil jwtUtil;
@Autowired
UserDetailServiceImpl userDetailService;
@Autowired
SysUserService sysUserService;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String token = request.getHeader(jwtUtil.getHeader());
// token 为空则放行, 后面在进行 相关的请求权限判断
// 若不为空 则进行身份验证
if(StrUtil.isBlankOrUndefined(token)){
chain.doFilter(request,response);
return ;
}
Claims claim = jwtUtil.getClaimsToken(token);
if(claim ==null){
throw new JwtException("token 异常");
}
// 判断是否过期
if(jwtUtil.isTokenExpired(claim)){
throw new JwtException("token 已过期");
}
String username=claim.getSubject();
//获取用户的权限信息
SysUser sysUser = sysUserService.getByUsername(username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
= new UsernamePasswordAuthenticationToken(username,null,userDetailService.getUserAuthority(sysUser.getId()));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
chain.doFilter(request,response);
}
}
@Configuration
@EnableWebSecurity // 开启Security 的安全策略
@EnableGlobalMethodSecurity(prePostEnabled = true) // 在post 请求前进行权限校验
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
LoginFailureHandler loginFailureHandler;
@Autowired
LoginSuccessHandler loginSuccessHandler;
@Autowired
CaptchaFilter captchaFilter;
@Bean
JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
JwtAuthenticationFilter jwtAuthenticationFilter
= new JwtAuthenticationFilter(authenticationManager());
return jwtAuthenticationFilter;
}
@Autowired
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
JwtAccessDeniedHandler jwtAccessDeniedHandler;
@Bean
BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Autowired
UserDetailServiceImpl userDetailService;
@Autowired
JwtLogoutSuccessHander jwtLogoutSuccessHander;
private static final String[] URL_WHILELIST={
"/login","/logout",
"/captcha","/favicon.ico","/test/**"
};
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
// 登录配置
.formLogin()
// 登录成功或者失败后,对应进行回调
.successHandler(loginSuccessHandler)
.failureHandler(loginFailureHandler)
// 退出登录 配置
.and()
.logout()
.logoutSuccessHandler(jwtLogoutSuccessHander)
// 禁用session
.and()
//设置无状态的连接,即不创建session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 配置拦截规则
.and()
// 除了 白名单的请求 放过,其他正常拦截
.authorizeRequests()
.antMatchers(URL_WHILELIST).permitAll()
.anyRequest().authenticated()
// 异常处理器
.and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint) // 认证失败 异常处理入口
.accessDeniedHandler(jwtAccessDeniedHandler) // 配置 权限不足 处理器
// 配置自定义的过滤器
.and()
.addFilter(jwtAuthenticationFilter())
.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class) // 登录验证码过滤器
;
}
/**
* 配置
* 将 userDetailService的实现类 注入到 security 中
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService);
}
}