一、Jwt
1.引入Jwt依赖
以下内容皆为针对该依赖提供。
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
2、生成token
JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。生成token时需要配置token的主体部分中的字段值(并非每个字段都需要配置),通过引入的依赖中的Jwts类提供的对应的set方法即可配置对应的字段值,如下:
字段名 | 含义 | 对应Jwts的方法 |
iss | 发行人 | setIssuer() |
exp | 到期时间 | setExpiration() |
sub | 主题 | setSubject() |
aud | 用户 | setAudience() |
nbf | 在此之前不可用 | setNotBefore() |
iat | 发布时间 | setIssuedAt() |
jti | JWT ID用于标识该JWT | setId() |
Jwts存储用户设置有效载荷部分的字段是通过底层JwtMap类中定义的Map存放这些字段的key-value键值对实现,下面是JwtMap的部分源码。
public class JwtMap implements Map<String, Object> {
private final Map<String, Object> map;
在Claims接口中定义了对应的get()、set() 抽象方法用于向map中存放对应的键值对,DefaultClaims类继承了JwtMap类并实现了Claims接口中的方法。
Claims接口的部分源码:
public interface Claims extends Map<String, Object>, ClaimsMutator<Claims> {
String ISSUER = "iss";
String SUBJECT = "sub";
String AUDIENCE = "aud";
String EXPIRATION = "exp";
String NOT_BEFORE = "nbf";
String ISSUED_AT = "iat";
String ID = "jti";
String getIssuer();
Claims setIssuer(String var1);
DefaultClaims类的部分源码:
public class DefaultClaims extends JwtMap implements Claims {
public DefaultClaims() {
}
public DefaultClaims(Map<String, Object> map) {
super(map);
}
public String getIssuer() {
return this.getString("iss");
}
public Claims setIssuer(String iss) {
this.setValue("iss", iss);
return this;
}
用户通过调用对应的get()、set()方法即可实现对jwt中字段的值(map)填充,填充后调用compact()方法生成token,该方法会根据用户配置生成jwt标头,并将用户填充的map转为base64加密后的字符串,还会根据用户配置的签证密钥生成一串签证字符串,最后将三者拼接成最终的jwt串返回,下面是生成jwt方法源码。
Header header = this.ensureHeader();
Key key = this.key;
if (key == null && !Objects.isEmpty(this.keyBytes)) {
key = new SecretKeySpec(this.keyBytes, this.algorithm.getJcaName());
}
Object jwsHeader;
if (header instanceof JwsHeader) {
jwsHeader = (JwsHeader)header;
} else {
jwsHeader = new DefaultJwsHeader(header);
}
if (key != null) {
((JwsHeader)jwsHeader).setAlgorithm(this.algorithm.getValue());
} else {
((JwsHeader)jwsHeader).setAlgorithm(SignatureAlgorithm.NONE.getValue());
}
if (this.compressionCodec != null) {
((JwsHeader)jwsHeader).setCompressionAlgorithm(this.compressionCodec.getAlgorithmName());
}
//生成base64jwt标头
String base64UrlEncodedHeader = this.base64UrlEncode(jwsHeader, "Unable to serialize header to json.");
String base64UrlEncodedBody;
if (this.compressionCodec != null) {
byte[] bytes;
try {
bytes = this.payload != null ? this.payload.getBytes(Strings.UTF_8) : this.toJson(this.claims);
} catch (JsonProcessingException var9) {
throw new IllegalArgumentException("Unable to serialize claims object to json.");
}
base64UrlEncodedBody = TextCodec.BASE64URL.encode(this.compressionCodec.compress(bytes));
} else {
//this.claims中包含用户配置的jwt信息的map,将其转为base64
base64UrlEncodedBody = this.payload != null ? TextCodec.BASE64URL.encode(this.payload) : this.base64UrlEncode(this.claims, "Unable to serialize claims object to json.");
}
String jwt = base64UrlEncodedHeader + '.' + base64UrlEncodedBody;
if (key != null) {
//key为用户配置的密钥,用于生成签证。
JwtSigner signer = this.createSigner(this.algorithm, (Key)key);
String base64UrlSignature = signer.sign(jwt);
jwt = jwt + '.' + base64UrlSignature;
} else {
jwt = jwt + '.';
}
return jwt;
代码中的具体使用:
String token = Jwts.builder()
.setId("jwt唯一标识")
.setSubject("jwt主题")
.setIssuedAt(new Date())
.claim("该部分可放入自定义信息,也可不放")
.setIssuer("zcg")
.setExpiration(new Date(System.currentTimeMillis() + "配置过期时间,单位ms"))
.signWith(SignatureAlgorithm.HS512, JWTConfig.secret)
.compact();
return token;
3、登录验证token
解析部分即对token三部分的base64解码,主要是将有效载荷部分的数据解析后放入Claims类中共用户直接获取token中的信息(注:源码中会获取当前时间与token的过期时间进行对比,若token过期则会直接抛出ExpiredJwtException异常)。
//解析Jwt
Claims claims = Jwts.parser()
.setSigningKey(token密钥)
.parseClaimsJws(token字符串)
.getBody();
在具体使用中需要定义请求过滤器用于验证用户发请求时携带的token。可以通过继承BasicAuthenticationFilter并覆盖其doFilterInternal方法实现具体的验证逻辑,下面是过滤器部分代码。
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//获取请求头中的JWT的Token
String tokenHeader = request.getHeader(JWTConfig.tokenHeader);
if (null != tokenHeader) {
//解析JWT
Claims claims = Jwts.parser()
.setSigningKey(JWTConfig.secret)
.parseClaimsJws(tokenHeader)
.getBody();
//获取权限
String auth = claims.get("authorities").toString();
//获取用户名与唯一标识
String username = claims.getSubject();
Integer id = Integer.parseInt(claims.getId());
//获取过期时
long expTime = claims.getExpiration().getTime();
二、SpringSecurity
1.引入SpringSecurity依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.定义登录成功处理器
定义登录成功类,并继承AuthenticationSuccessHandler,实现其onAuthenticationSuccess方法,在该方法中生成token并返回,前端每次请求接口时携带该token,拦截器再进行验证token操作。
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
//生成token,生成方法上文已贴代码
String token = JWTTokenUtil.createAccessToken(userSecurity);
//自定义方法通过response返回token
ResultUtil.responseJson(response, resultData);
3.配置SpringSecurity
定义类继承WebSecurityConfigurerAdapter并覆盖configure方法配置相关处理器,一以下是我的全部配置,token主要是通过上文编写的登录成功处理器生成并返回的,同时配置里也需要配置上过滤token需要的上文实现的token过滤器。
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(JWTConfig.antMatchers.split(",")).permitAll()
// 其他的需要登陆后才能访问 其他url都需要验证
.anyRequest().authenticated()
.and()
//未登录处理逻辑
.httpBasic()
.authenticationEntryPoint(userAuthenticationEntryPointHandler)
.and()
//表单登录相关配置
.formLogin()
//登录地址
.loginPage("/login")
//提交form表单的请求地址
.loginProcessingUrl("/security/login/myLoginForm")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
//登录成功处理
.successHandler(userLoginSuccessHandler)
//登录失败处理
.failureHandler(userLoginFailureHandler)
.and()
//退出登录
.logout()
//退出登录路径
.logoutUrl("/security/logout")
//退出成功处理
.logoutSuccessHandler(userLogoutSuccessHandler)
.and()
//开启跨域
.cors()
.and()
//取消跨站请求伪造防护
.csrf().disable();
// 基于Token不需要session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 禁用缓存
http.headers().cacheControl();
// 添加JWT过滤器
http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager()));
}