一、过滤器filter
1.登录的时候后台根据用户ID和用户名生成一个带有时效的token
2.前端获取道token,写入cookies
3.前端请求的时候带上token
4.后端解析token,token为空,或者失效视为认证失败!
5.前端统一封装请求,如果是认证失败的,跳回登录页面
涉及工具、方法:
(1)过滤器Filter实现拦截
(2)过滤器注册到spring容器
(3)jjwt实现token生成和解析
(4)/login的地址不拦截
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xy.service</groupId>
<artifactId>my-springboot-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>my-springboot-service</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!--<scope>runtime</scope>-->
<!--<optional>true</optional>-->
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
<!--json-->
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.3</version>
<classifier>jdk15</classifier>
</dependency>
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork><!-- 如果没有该配置,热部署的devtools不生效 -->
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
RestfulRequestFilter.java 过滤器
package com.app.service.filter;
import com.app.service.constant.CommonConstant;
import com.app.service.utils.resp.ResponseUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import net.sf.json.JSONObject;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class RestfulRequestFilter implements Filter {
private static final long serialVersionUID = 1L;
public RestfulRequestFilter() {
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS,PUT");
response.setHeader("Access-Control-Max-Age", "0");
response.setHeader("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With");
response.setHeader("Allow", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH");
if (request.getMethod().equals("OPTIONS")) {
response.setStatus(200);
} else {
String url = request.getRequestURL().toString();
String urlParams = request.getQueryString();
String contnetType = request.getContentType();
if (this.ifNotNeedCheck(url)) {
chain.doFilter(req, res);
} else {
int resultCd = this.checkAccess(request, response);
String result = "";
switch(resultCd) {
case 0:
chain.doFilter(req, res);
return;
case 1:
result = ResponseUtils.error(401,"认证失败!");
break;
default:
result = ResponseUtils.error(401,"认证失败!");
}
response.getOutputStream().write(result.getBytes("UTF-8"));
response.setContentType("text/json; charset=UTF-8");
}
}
}
private boolean ifNotNeedCheck(String url) {
String[] acceptUrls = new String[]{"/login"};
for(int i = 0; i < acceptUrls.length; ++i) {
if (url.indexOf(acceptUrls[i]) > -1) {
return true;
}
}
return false;
}
private int checkAccess(HttpServletRequest request, HttpServletResponse response) throws ServletException {
String authHeader = request.getHeader("Authorization");
if (authHeader == null) {
return 1;
} else {
String token = authHeader;
try {
JSONObject staffInfoJo = parseToken(token);
request.setAttribute("userId", staffInfoJo.getString("userId"));
request.setAttribute("Authorization", authHeader);
return 0;
} catch (Exception var6) {
return 2;
}
}
}
/**
* 解析token
* @param token
* @return
*/
public static JSONObject parseToken(String token){
JSONObject staffInfoJo = null;
try {
final Claims claims = Jwts.parser().setSigningKey(CommonConstant.SECRET_KEY).
parseClaimsJws(token).getBody();
staffInfoJo = JSONObject.fromObject(claims.getSubject());
} catch (Exception e) {
final Claims claims = Jwts.parser().setSigningKey("secretkey")
.parseClaimsJws(token).getBody();
staffInfoJo = JSONObject.fromObject(claims.getSubject());
}
return staffInfoJo;
}
public static void main(String[] args) {
JSONObject staffInfojo = new JSONObject();
staffInfojo.put("userId", "1001");
staffInfojo.put("userName", "jack");
// String token = Jwts.builder().setSubject(staffInfojo.toString()).claim("pwd", "hahaha,if you think this is pwd,then you are wrong").setIssuedAt(new Date()).signWith(SignatureAlgorithm.HS256, "10000_YEARS_PPM").setExpiration(new Date(System.currentTimeMillis() + 2592000000L)).compact();
// LOG.debug(token);
}
}
FilterConfig.java 加入spring容器
package com.app.service.config;
import com.app.service.filter.RestfulRequestFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new RestfulRequestFilter());
bean.addUrlPatterns("/*");
return bean;
}
}
controller 生成token
String token = JWTUtil.genToken(jsonObject);
Map<String, Object> result = new HashMap<>();
result.put("token", token) ResponseUtils.success(result);
token工具类
package com.app.service.utils;
import com.app.service.constant.CommonConstant;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
/**
* JWT 工具类
* JSON Web Token (JWT)是一种基于 token 的认证方案
* 简单的说,JWT就是一种Token的编码算法,服务器端负责根据一个密码和算法生成Token,然后发给客户端,客户端只负责后面每次请求都在HTTP header里面带上这个Token,服务器负责验证这个Token是不是合法的,有没有过期等,并可以解析出subject和claim里面的数据。
注意:JWT里面的数据是BASE64编码的,没有加密,因此不要放如敏感数据
*
*/
public class JWTUtil {
/**
* 生成token
* @param obj
* @return
*/
public static String genToken(Object obj){
String token = Jwts.builder()
.setSubject(obj.toString()) // 账号
.claim("pwd", "I'm password")
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, CommonConstant.SECRET_KEY)
.setExpiration(new Date(System.currentTimeMillis() + 15*1000L)) // token有效期:1天
.compact();
// 1天 3600*24*1000L
// 1分钟 60*1000L
// 15秒 15*1000L
return token;
}
public static String getExpireToken() throws Exception {
String expireToken = Jwts.builder()
.setSubject("exipre account") // 账号
.claim("pwd", "I'm password")
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256,CommonConstant.SECRET_KEY)
.setExpiration(new Date(System.currentTimeMillis() - 5000L)) // 直接失效
.compact();
return expireToken;
}
public static void main(String[] args) {
JSONObject staffInfojo = new JSONObject();
staffInfojo.put("userId", "1001");
staffInfojo.put("userName", "jack");
//生成一个token,用来测试
System.out.println(genToken(staffInfojo));
}
}
controller
package com.xy.service.controller;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class TestController {
@RequestMapping(value = "/hello", produces = "application/json; charset=UTF-8", method = RequestMethod.POST)
// @AuthIgnore
public Map<String,Object> queryCustomerList(@RequestHeader("token") String token) {
Map<String, Object> result = new HashMap<>();
result.put("code","200");
result.put("msg","成功");
return result;
}
}
用 JWTUtil跑出来的测试token,放到header里面,运行
运行成功:
没有传token呢,或者是过期的token就会认证失败
二、拦截器
JWTInterceptor
package com.xy.service.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xy.service.constant.CommonConstant;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class JWTInterceptor extends HandlerInterceptorAdapter {
public static JSONObject parseToken(String token){
JSONObject staffInfoJo = null;
try {
final Claims claims = Jwts.parser().setSigningKey(CommonConstant.SECRET_KEY).
parseClaimsJws(token).getBody();
staffInfoJo = JSONObject.fromObject(claims.getSubject());
} catch (Exception e) {
final Claims claims = Jwts.parser().setSigningKey("secretkey")
.parseClaimsJws(token).getBody();
staffInfoJo = JSONObject.fromObject(claims.getSubject());
}
return staffInfoJo;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.debug("{}",request.getRequestURL());
if(!(handler instanceof HandlerMethod)) {
return true;
}
AuthIgnore annotation = ((HandlerMethod) handler).getMethodAnnotation(AuthIgnore.class);
if (annotation != null && !annotation.required()) {
return true;
}
Map<Object, Object> map = new HashMap<>();
String authHeader = request.getHeader("token");
if (authHeader == null) {
map.put("msg","token无效!");
} else {
String token = authHeader;
try {
JSONObject staffInfoJo = parseToken(token);
request.setAttribute("userId", staffInfoJo.getString("userId"));
request.setAttribute("Authorization", authHeader);
map.put("state",true);
map.put("msg","请求成功");
return true;
} catch (Exception e) {
e.printStackTrace();
map.put("msg","token无效!");
}
}
//将 map装换为json ResponseBody底层使用jackson
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}
GlobalWebMvcConfig
package com.xy.service.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Slf4j
@Configuration
public class GlobalWebMvcConfig implements WebMvcConfigurer {
/**
* 重写父类提供的跨域请求处理的接口
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 添加映射路径
/* registry.addMapping("/**")
// 放行哪些原始域
// .allowedOrigins("*")
// 是否发送Cookie信息
.allowCredentials(true)
// 放行哪些原始域(请求方式)
.allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS", "HEAD")
// 放行哪些原始域(头部信息)
.allowedHeaders("*")
// 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
.exposedHeaders("Server","Content-Length", "Authorization", "Access-Token", "Access-Control-Allow-Origin","Access-Control-Allow-Credentials");
*/
}
/**
* 添加拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加权限拦截器
registry.addInterceptor(new JWTInterceptor()).addPathPatterns("/**");
}
}
运行,同样的进行token认证