springboot + 注解 + 拦截器 + JWT 实现角色权限控制
1、关于JWT,参考:
(2)认识JWT
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 { // 401 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、测试
posted on 2019-06-03 19:06 wenbin_ouyang 阅读(1952) 评论(0) 编辑 收藏 举报