木心

毕竟几人真得鹿,不知终日梦为鱼

导航

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

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 {
            // 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  阅读(1751)  评论(0编辑  收藏  举报