1、关于JWT,参考:

  (1)10分钟了解JSON Web令牌(JWT)

  (2)认识JWT

  (3)基于jwt的token验证

2、JWT的JAVA实现

Java中对JWT的支持可以考虑使用JJWT开源库;JJWT实现了JWT, JWS, JWE 和 JWA RFC规范;下面将简单举例说明其使用:

2.1、生成Token码

import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import io.jsonwebtoken.*;
import java.util.Date; //Sample method to construct a JWT
private String createJWT(String id, String issuer, String subject, long ttlMillis) {   //The JWT signature algorithm we will be using to sign the token
  SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;   long nowMillis = System.currentTimeMillis();
  Date now = new Date(nowMillis);   //We will sign our JWT with our ApiKey secret
  byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(apiKey.getSecret());
  Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());   //Let's set the JWT Claims
  JwtBuilder builder = Jwts.builder().setId(id)
.setIssuedAt(now)
.setSubject(subject)
.setIssuer(issuer)
.signWith(signatureAlgorithm, signingKey);   //if it has been specified, let's add the expiration
  if (ttlMillis >= 0) {
  long expMillis = nowMillis + ttlMillis;
  Date exp = new Date(expMillis);
  builder.setExpiration(exp);
  }   //Builds the JWT and serializes it to a compact, URL-safe string
  return builder.compact();
}

2.2、解码和验证Token码

import javax.xml.bind.DatatypeConverter;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.Claims; //Sample method to validate and read the JWT
private void parseJWT(String jwt) {
  //This line will throw an exception if it is not a signed JWS (as expected)
  Claims claims = Jwts.parser()
  .setSigningKey(DatatypeConverter.parseBase64Binary(apiKey.getSecret()))
  .parseClaimsJws(jwt).getBody();
  System.out.println("ID: " + claims.getId());
  System.out.println("Subject: " + claims.getSubject());
  System.out.println("Issuer: " + claims.getIssuer());
  System.out.println("Expiration: " + claims.getExpiration());
}

3、springboot + 注解 + 拦截器 + JWT 实现角色权限控制

  demo涉及的技术:springboot 2.1.5.RELEASE + 注解 + 拦截器 + JWT。

  demo功能:模拟角色权限控制。使用注解@Role标注的Controller方法,都需要进行token验证,判断token中role的值与@Role注解中role的值是否相同。

  

3.1、pom文件

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
</dependencies>

3.2、token工具类JwtUtil

package com.oy.util;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import com.oy.model.User;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; public class JwtUtil {
public static final long EXPIRATION_TIME = 3600_000; // 1 hour
// public static final long EXPIRATION_TIME = 0; // for test
public static final String SECRET = "secret007";
public static final String TOKEN_PREFIX = "Bearer";
public static final String HEADER_STRING = "Authorization"; // 生成token
public static String generateToken(User user) {
//you can put any data into the map
HashMap<String, Object> map = new HashMap<>();
map.put("name", user.getUsername());
map.put("role", user.getRole()); String jwt = Jwts.builder()
.setClaims(map) // 数据
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 过期时间
.signWith(SignatureAlgorithm.HS512, SECRET) // 算法,密钥
.compact();
return TOKEN_PREFIX + " " + jwt;
} // 验证和解析token
public static Map<String, Object> validateTokenAndGetClaims(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING); // Authorization
if (token == null)
throw new TokenValidationException("Missing token"); // Parse the token. Throw exception when token is invalid
Map<String, Object> body = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody();
return body;
} @SuppressWarnings("serial")
public static class TokenValidationException extends RuntimeException {
public TokenValidationException(String msg) {
super(msg);
}
} // 测试生成jwt
@Test
public void test1() {
UtilFunctions.log.info(generateToken(new User("admin", "", "admin")));
// Bearer eyJhbGciOiJIUzUxMiJ9.eyJyb2xlIjoiYWRtaW4iLCJuYW1lIjoiYWRtaW4iLCJleHAiOjE1NjAwNzI1MTN9.YRG59eqP8nIoRNURPRYZWv3SAtss9YLOXjRsVmLmms7qMImq4MsERN0QuDbLGorgLCAbrIiSJjBY5_DaPJqP6Q
} // 测试验证和解析token
// 测试使用错误的密钥: io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature.
// JWT validity cannot be asserted and should not be trusted.
@Test
public void test2() {
String token = "eyJhbGciOiJIUzUxMiJ9.eyJyb2xlIjoiYWRtaW4iLCJuYW1lIjoiYWRtaW4iLCJleHAiOjE1NjAwNzI1MTN9.YRG59eqP8nIoRNURPRYZWv3SAtss9YLOXjRsVmLmms7qMImq4MsERN0QuDbLGorgLCAbrIiSJjBY5_DaPJqP6Q";
Map<String, Object> body = Jwts.parser()
//.setSigningKey(SECRET)
.setSigningKey("secret") // 测试使用错误的密钥
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody(); for (Entry<String, Object> entry : body.entrySet()) {
UtilFunctions.log.info("=== key:{}, value:{} ===", entry.getKey(), entry.getValue());
// === key:role, value:admin ===
// === key:name, value:admin ===
// === key:exp, value:1560072513 ===
}
}
}

3.3、注解@Role的定义

package com.oy.filter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Role {
String role();
}

3.4、拦截器RoleInterceptor

package com.oy.filter;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.oy.util.AuthException;
import com.oy.util.JwtUtil;
import com.oy.util.UtilFunctions; public class RoleInterceptor extends HandlerInterceptorAdapter { @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception { UtilFunctions.log.info("RoleInterceptor work..."); // target of request is method of controller
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Role roleAnnotation = handlerMethod.getMethodAnnotation(Role.class); if (roleAnnotation == null) { // method without @Role annotation
UtilFunctions.log.info("RoleInterceptor over...");
return true; // 放行
} else { // method with @Role annotation
// 验证token
String role = null;
try {
Map<String, Object> claims = JwtUtil.validateTokenAndGetClaims(request);
UtilFunctions.log.info("claims:{}", claims);
role = String.valueOf(claims.get("role")); // 从token中取数据: role
// roleAnnotation.role(): 获取注解中指定role
UtilFunctions.log.info("=== role:{}, roleAnnotation.role:{} ===", role, roleAnnotation.role()); if (System.currentTimeMillis() / 1000L > (int) claims.get("exp")) {
throw new AuthException("token 过期了...");
}
} catch (Exception e) {
// response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
// UtilFunctions.log.info(e.toString() + ". RoleInterceptor over...");
// return false; // 拦截
// token验证不通过,拦截
throw new AuthException(e.getMessage());
} if (role == null || !role.equals(roleAnnotation.role())) {
// response.setStatus(401);
// UtilFunctions.log.info("RoleInterceptor over...");
// return false; // 拦截
throw new AuthException("a role of " + roleAnnotation.role() + " is needed, but you are " + role);
}
}
} UtilFunctions.log.info("RoleInterceptor over...");
return true; // // 放行
} }

  在sprigboot中注册拦截器

package com.oy;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.oy.filter.RoleInterceptor; @EnableWebMvc
@Configuration
public class webConfig implements WebMvcConfigurer { @Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截除登陆外的所有请求
registry.addInterceptor(new RoleInterceptor())
.addPathPatterns("/**").excludePathPatterns("/login");
} }

3.5、IndexController

package com.oy.controller;
import java.util.HashMap;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.oy.filter.Role;
import com.oy.model.User;
import com.oy.util.JwtUtil;
import com.oy.util.Response;
import com.oy.util.UtilFunctions; @RestController
public class IndexController { @Role(role = "user")
@GetMapping("/test1")
public JSONObject test1() {
return new Response("test role=user").toJson();
} @Role(role = "admin")
@GetMapping("/test2")
public JSONObject test2() {
return new Response("test role=admin").toJson();
} // 登录方法不拦截
@SuppressWarnings({ "serial", "rawtypes" })
@PostMapping("/login")
public Object login(HttpServletResponse response,
@RequestParam(value = "username", required = true) String username,
@RequestParam(value = "password", required = true) String password) throws Exception { UtilFunctions.log.info("login info, username:{}, password:{}", username, password); User user = isValidUsernameAndPassword(username, password);
if (user != null) {
String jwt = JwtUtil.generateToken(user);
return new HashMap<String, String>() {
{
put("token", jwt);
}
};
} else {
//
return new ResponseEntity(HttpStatus.UNAUTHORIZED);
}
} private User isValidUsernameAndPassword(String username, String password) {
if ("admin".equals(username) && "admin123".equals(password)
|| "user".equals(username) && "user123".equals(password)) {
return new User("admin", "", "admin");
}
return null;
}
}

3.6、全局异常处理ExceptionHandlerController

package com.oy.controller;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus; import com.alibaba.fastjson.JSONObject;
import com.oy.util.AuthException; @ControllerAdvice
public class ExceptionHandlerController { @ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public JSONObject runtimeExceptionHandler(RuntimeException ex) {
JSONObject response = new JSONObject();
response.put("message", ex.getMessage());
return response;
} @ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public JSONObject exceptionHandler(Exception ex) {
JSONObject response = new JSONObject();
response.put("message", ex.getMessage());
return response;
} @ExceptionHandler(AuthException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ResponseBody
public JSONObject authExceptionHandler(Exception ex) {
JSONObject response = new JSONObject();
response.put("message", ex.getMessage());
return response;
}
}

3.7、其他model类或工具类

  User类

package com.oy.model;
public class User {
private String username;
private String password;
private String role;
public User() {}
public User(String username, String password, String role) {
this.username = username;
this.password = password;
this.role = role;
}
// getter和setter方法省略
}

  AuthException类

package com.oy.util;
@SuppressWarnings("serial")
public class AuthException extends RuntimeException {
public AuthException() {}
public AuthException(String message) {
super(message);
}
}

  Response类

package com.oy.util;
import com.alibaba.fastjson.JSONObject; public class Response {
private int code = 0;
private String msg = "";
private String data = "";
private JSONObject responseJson = new JSONObject(true); public Response(int code) {
this.code = code;
} public Response(int code, String msg) {
this.code = code;
this.msg = msg;
} public Response(String data) {
this.code = 0;
this.data = data;
} public String toString() {
responseJson.put("code", this.code);
if (!msg.isEmpty()) {
responseJson.put("message", this.msg);
}
if (!data.isEmpty()) {
responseJson.put("data", this.data);
} return responseJson.toJSONString();
} public JSONObject toJson() {
responseJson.put("code", this.code);
if (!msg.isEmpty()) {
responseJson.put("message", this.msg);
}
if (!data.isEmpty()) {
responseJson.put("data", this.data);
} return responseJson;
}
}

  UtilFunctions类

package com.oy.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UtilFunctions {
public static Logger log = LoggerFactory.getLogger("jwt");
}

3.8、测试

 

springboot + 注解 + 拦截器 + JWT 实现角色权限控制的更多相关文章

  1. struts2学习笔记--拦截器(Interceptor)和登录权限验证Demo

    理解 Interceptor拦截器类似于我们学过的过滤器,是可以在action执行前后执行的代码.是我们做web开发是经常使用的技术,比如权限控制,日志.我们也可以把多个interceptor连在一起 ...

  2. springboot + redis + 注解 + 拦截器 实现接口幂等性校验

    一.概念 幂等性, 通俗的说就是一个接口, 多次发起同一个请求, 必须保证操作只能执行一次 比如: 订单接口, 不能多次创建订单 支付接口, 重复支付同一笔订单只能扣一次钱 支付宝回调接口, 可能会多 ...

  3. Springboot + redis + 注解 + 拦截器来实现接口幂等性校验

    Springboot + redis + 注解 + 拦截器来实现接口幂等性校验   1. SpringBoot 整合篇 2. 手写一套迷你版HTTP服务器 3. 记住:永远不要在MySQL中使用UTF ...

  4. Spring MVC 方法注解拦截器

    应用场景,在方法级别对本次调用进行鉴权,如api接口中有个用户唯一标示accessToken,对于有accessToken的每次请求可以在方法加一个拦截器,获得本次请求的用户,存放到request或者 ...

  5. SpringBoot 注册拦截器方式及拦截器如何获取spring bean实例

    SpringBoot 注册拦截器时,如果用New对象的方式的话,如下: private void addTokenForMallInterceptor(InterceptorRegistry regi ...

  6. SpringMVC拦截器与SpringBoot自定义拦截器

    首先我们先回顾一下传统拦截器的写法: 第一步创建一个类实现HandlerInterceptor接口,重写接口的方法. 第二步在XML中进行如下配置,就可以实现自定义拦截器了 SpringBoot实现自 ...

  7. SpringMVC拦截器(资源和权限管理)

    1.DispatcherServlet SpringMVC具有统一的入口DispatcherServlet,所有的请求都通过DispatcherServlet.    DispatcherServle ...

  8. SpringBoot自定义拦截器实现IP白名单功能

    SpringBoot自定义拦截器实现IP白名单功能 转载请注明源地址:http://www.cnblogs.com/funnyzpc/p/8993331.html 首先,相关功能已经上线了,且先让我先 ...

  9. 11. SpringMVC拦截器(资源和权限管理)

    1.DispatcherServlet SpringMVC具有统一的入口DispatcherServlet,所有的请求都通过DispatcherServlet.     DispatcherServl ...

随机推荐

  1. mysql5.7无法启动原因排查

    前天刚刚拷了同事最新的MySQL5.7,安装成功后运行良好,今天却无法启动,Navicat也无法连接. 开始排查原因: 1.进入dos命令窗口,输入net start mysql启动,提示 百度出现错 ...

  2. OpenCV在ARM-linux上的移植过程遇到的问题3---共享库中嵌套库居然带路径【未解决】

    [Linux开发]OpenCV在ARM-linux上的移植过程遇到的问题3-共享库中嵌套库居然带路径[未解决] 标签(空格分隔): [Linux开发] 移植opencv到tq2440 一.下载open ...

  3. 深入理解java:1.1.1. 反射机制

    反射 到底什么是反射(Reflection)呢? 反射有时候也被称为内省(Introspection),事实上,反射,就是一种内省的方式, Java不允许在运行时改变程序结构或类型变量的结构,但它允许 ...

  4. centos7 无法启动网络(service network restart)错误解决办法(转)

    centos7 无法启动网络(service network restart)错误解决办法:(以下方法均为网上COPY,同时感谢原博主分享) systemctl status network.serv ...

  5. JS基础篇--JS获取元素的宽高以及offsetTop,offsetLeft等的属性值

    $(obj).width()与$(obj).height() $(obj).width()与$(obj).height() :jquery方式获取元素的宽高,不包括滚动条与工具条 $(obj).wid ...

  6. linux 进程1

    一. 进程的开始和结束 1.1. main函数的调用 a. 编译链接时的引导代码.操作系统下的应用程序其实在main执行前也需要先执行一段引导代码才能去执行main,我们写应用程序时不用考虑引导代码的 ...

  7. Linear Discriminant Analysis

    Suppose that we model each class density as multivariate Gaussian, in practice we do not know the pa ...

  8. Python 入门之常用运算符

    Python 入门之常用运算符 Python中的运算按种类可分为算数运算.比较运算.逻辑运算.赋值运算.成员运算.身份运算.位运算 1.常用运算符: (1)算数运算符: + - * / %(取余(模) ...

  9. bootstrap使用总结(carousel设置大小。item设置大小,img设置大小)

    在bootstrap中使用carousel,先要给.carousel一个大小, 要想使carousel和item和img随着浏览器大小而变,就要设置 .carousel .item { height: ...

  10. Fusioncharts图表常用参数设置

    1.1 <chart>参数设置: 图表和轴的标题* caption=”String” : 图表上方的标题* subCaption=”String” : 图表上方的副标题* xAxisNam ...