基于jwt的Token认证机制可以看之前的文章:
在前后端分离中,我们与前端约定一种身份认证机制。当用户登录的时候,我们会返回给前端一个token,前端会将token拿到并按照一定规则放到header中在下一次请求中发送给后端,后端进行token身份校验。
这里我们约定前端请求后端服务时需要添加头信息Authorization ,内容为Test:+空格+token
1.导入pom依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.49</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2.jwt工具类
@ConfigurationProperties("jwt.config")
@Component
@Data
public class JwtUtils {
/**
* 签名私钥
*/
private String key;
/**
* 签名的失效时间
*/
private Long ttl;
/**
* 设置认证token
* id:登录用户id
* subject:登录用户名
*/
public String createJwt(String id, String name, Map<String, Object> map) {
//1.设置失效时间
long now = System.currentTimeMillis();
long exp = now + ttl;
//2.创建jwtBuilder
JwtBuilder jwtBuilder = Jwts.builder().setId(id).setSubject(name)
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, key);
//3.根据map设置claims
for (Map.Entry<String, Object> entry : map.entrySet()) {
jwtBuilder.claim(entry.getKey(), entry.getValue());
}
jwtBuilder.setExpiration(new Date(exp));
//4.创建token
return jwtBuilder.compact();
}
/**
* 解析token字符串获取clamis
*/
public Claims parseJwt(String token) {
try {
return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
} catch (Exception e) {
return null;
}
}
}
3.Spring的yml配置
jwt:
config:
key: test
ttl: 360000
server:
port: 10001
4.全局常量类
public class GlobalConstant {
public static final String AUTHORIZATION = "Authorization";
public static final String USER_KEY = "user_key";
}
5.通用返回类
前后端分离中,通常会与前端约定一个默认的返回格式,并定义一些不同场景下的状态码。
Result.java
@Data
@NoArgsConstructor
public class Result {
private boolean success;//是否成功
private Integer code;// 返回码
private String message;//返回信息
private Object data;// 返回数据
public Result(ResultCode code) {
this.success = code.success;
this.code = code.code;
this.message = code.message;
}
public Result(ResultCode code, Object data) {
this.success = code.success;
this.code = code.code;
this.message = code.message;
this.data = data;
}
public Result(Integer code, String message, boolean success) {
this.code = code;
this.message = message;
this.success = success;
}
public static Result SUCCESS(){
return new Result(ResultCode.SUCCESS);
}
public static Result ERROR(){
return new Result(ResultCode.SERVER_ERROR);
}
public static Result FAIL(){
return new Result(ResultCode.FAIL);
}
}
ResultCode.java
public enum ResultCode {
SUCCESS(true,10000,"操作成功!"),
//---系统错误返回码-----
FAIL(false,10001,"操作失败"),
UNAUTHENTICATED(false,10002,"您还未登录"),
UNAUTHORISE(false,10003,"权限不足"),
SERVER_ERROR(false,99999,"抱歉,系统繁忙,请稍后重试!"),
//---用户操作返回码 2xxxx----
MOBILEORPASSWORDERROR(false,20001,"用户名或密码错误");
//---企业操作返回码 3xxxx----
//---权限操作返回码----
//---其他操作返回码----
//操作是否成功
boolean success;
//操作代码
int code;
//提示信息
String message;
ResultCode(boolean success,int code, String message){
this.success = success;
this.code = code;
this.message = message;
}
public boolean success() {
return success;
}
public int code() {
return code;
}
public String message() {
return message;
}
}
6.拦截器注解
在我们后端的接口中,并不是所有接口都需要拦截,比如登录接口就不需要登录就能访问,比如有的接口有游客模式也不需要登录访问。我们自定义一个注解,然后在接下来的全局拦截器中去特殊处理这些不需要拦截的接口。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExcludeInterceptor {
boolean value() default true;
}
7.拦截器
利用Spring的Intercepter拦截器,拦截所有请求,去校验用户是否登录。同时将用户的id获取到,设置到session中,方便代码直接获取使用。
注意:为了防止拦截器二次拦截,需要配置不拦截/error请求
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Autowired
private JwtUtils jwtUtils;
private Logger logger = LoggerFactory.getLogger(this.getClass());
protected static Collection<HandlerMethodArgumentResolver> methodArgumentResolverList;
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
super.addArgumentResolvers(argumentResolvers);
if (methodArgumentResolverList != null) {
argumentResolvers.addAll(methodArgumentResolverList);
}
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "OPTIONS", "DELETE", "PATCH")
.allowCredentials(true).maxAge(3600);
}
/**
* FastJson配置
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(fastJsonHttpMessageConverters());
}
@Bean
public HttpMessageConverter fastJsonHttpMessageConverters() {
//1. 需要定义一个converter转换消息的对象
FastJsonHttpMessageConverter fasHttpMessageConverter = new FastJsonHttpMessageConverter();
//2. 添加fastjson的配置信息,比如:是否需要格式化返回的json的数据
FastJsonConfig fastJsonConfig = new FastJsonConfig();
//3. 处理中文乱码问题
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fasHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
//4. 在converter中添加配置信息
fasHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
return fasHttpMessageConverter;
}
@Bean
public SecurityInterceptor getSecurityInterceptor() {
return new SecurityInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration interceptor = registry.addInterceptor(getSecurityInterceptor());
ArrayList<String> list = new ArrayList<>();
list.add("/error");
list.add("/static/**");
interceptor.excludePathPatterns(list);
super.addInterceptors(registry);
}
protected class SecurityInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(request.getMethod());
if ("OPTIONS".equals(request.getMethod())) {
if (response != null) {
response.setStatus(200);
response.sendError(200, "通过");
}
return false;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
ExcludeInterceptor excludeInterceptor = handlerMethod.getMethodAnnotation(ExcludeInterceptor.class);
if (excludeInterceptor == null || !excludeInterceptor.value()) {
if (request.getHeader(GlobalConstant.AUTHORIZATION) == null) {
response.setStatus(401);
response.sendError(401, "登录失效,请重新登录");
return false;
} else {
String authorization = request.getHeader(GlobalConstant.AUTHORIZATION);
String token = authorization.replace("Test: ", "");
Claims claims = jwtUtils.parseJwt(token);
if (claims == null) {
response.setStatus(401);
response.sendError(401, "登录失效,请重新登录");
return false;
}
String userId = claims.getId();
request.getSession().setAttribute(GlobalConstant.USER_KEY, userId);
}
}
return true;
}
}
}
8.测试controller
@RestController
public class LoginController {
@Autowired
private JwtUtils jwtUtils;
@PostMapping("/login")
@ResponseBody
@ExcludeInterceptor
public Result login(@RequestBody Map<String, String> loginMap) {
String mobile = loginMap.get("username");
String password = loginMap.get("password");
//登录失败
if (mobile == null || !password.equals("123456")) {
return new Result(ResultCode.MOBILEORPASSWORDERROR);
}
//登录成功
String token = jwtUtils.createJwt("1", "zhangsan", new HashMap<>());
return new Result(ResultCode.SUCCESS, token);
}
@GetMapping("/info")
@ResponseBody
public Result getUserInfo(@SessionAttribute(GlobalConstant.USER_KEY) String userId) {
//模拟获取该用户的信息
HashMap<Object, Object> map = new HashMap<>();
map.put("username", "admin");
return new Result(ResultCode.SUCCESS, map);
}
}
9.测试
启动项目,用postman去访问该项目的登录接口,模拟用户登录

登录成功,返回token
{
"code": 10000,
"data": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxIiwic3ViIjoiemhhbmdzYW4iLCJpYXQiOjE1Njg3ODc5NzUsImV4cCI6MTU2ODc4ODMzNX0.8tAvJgytFIKk2wwXRAsJ3IQV51SxnzXCFmmUcAPdbUI",
"message": "操作成功!",
"success": true
}
登录成功后,将返回的token设置到header中,去访问其它接口

如果Authorization值被伪造或过期:

6324

被折叠的 条评论
为什么被折叠?



