shiro+jwt登录授权

/ 工具和中间件springbootjava / 0 条评论 / 931浏览

shiro+jwt登录授权

public class JwtToken implements AuthenticationToken {

    private String jwt;

    public JwtToken(String jwt){
        this.jwt = jwt;
    }

    @Override
    public Object getPrincipal() {
        return jwt;
    }

    @Override
    public Object getCredentials() {
        return jwt;
    }
}
public class JwtUtil {

    // 创建默认的秘钥和算法,供无参的构造方法使用
    private static final String defaultBase64EncodedSecretKey = "B*B^";
    private static final SignatureAlgorithm defaultSignatureAlgorithm = SignatureAlgorithm.HS256;

    public JwtUtil() {
        this(defaultBase64EncodedSecretKey, defaultSignatureAlgorithm);
    }

    private final String base64EncodedSecretKey;
    private final SignatureAlgorithm signatureAlgorithm;

    public JwtUtil(String secretKey, SignatureAlgorithm signatureAlgorithm) {
        this.base64EncodedSecretKey = Base64.encodeBase64String(secretKey.getBytes());
        this.signatureAlgorithm = signatureAlgorithm;
    }

    /**
     * 用户token鉴权(判断jwtToken是否合法)
     * @param jwtToken
     * @return
     */
    public boolean isVerify(String jwtToken) {
        // 这个是官方的校验规则,这里只写了一个”校验算法“,
        Algorithm algorithm = null;
        switch (signatureAlgorithm){
            case HS256:
                algorithm = Algorithm.HMAC256(Base64.decodeBase64(base64EncodedSecretKey));
                break;
            default:
                throw new RuntimeException("不支持该算法");
        }
        JWTVerifier verifier = JWT.require(algorithm).build();
        // 校验不通过会抛出异常
        // 判断合法的标准:1. 头部和荷载部分没有篡改过。2. 没有过期
        verifier.verify(jwtToken);
        return true;
    }

    /**
     * 用户信息解密(生成对应的username和password等字段)
     * @param jwtToken
     * @return
     */
    public Claims decode(String jwtToken) {
        return Jwts.parser()
                // 设置签名的密钥
                .setSigningKey(base64EncodedSecretKey)
                // 设置需要解密的jwt
                .parseClaimsJws(jwtToken)
                .getBody();
    }

    /**
     * 这里就是产生jwt字符串的地方
     * jwt字符串包括三个部分
     *  1. header
     *      -当前字符串的类型,一般都是“JWT”
     *      -哪种算法加密,“HS256”或者其他的加密算法
     *      所以一般都是固定的,没有什么变化
     *  2. payload
     *      一般有四个最常见的标准字段(下面有)
     *      iat:签发时间,也就是这个jwt什么时候生成的
     *      jti:JWT的唯一标识
     *      iss:签发人,一般都是username或者userId
     *      exp:过期时间
     * */
    public String encode(String issuer, long ttlMillis, Map<String, Object> claims) {
        // iss签发人,ttlMillis生存时间,claims是指还想要在jwt中存储的一些非隐私信息
        if (claims == null) {
            claims = new HashMap<>();
        }
        long nowMillis = System.currentTimeMillis();

        JwtBuilder builder = Jwts.builder()
                // 荷载部分
                .setClaims(claims)
                // 这个是JWT的唯一标识,一般设置成唯一的,这个方法可以生成唯一标识
                .setId(UUID.randomUUID().toString())//2.
                // 签发时间
                .setIssuedAt(new Date(nowMillis))
                // 签发人,也就是JWT是给谁的(逻辑上一般都是username或者userId)
                .setSubject(issuer)
                .signWith(signatureAlgorithm, base64EncodedSecretKey);//这个地方是生成jwt使用的算法和秘钥
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);//4. 过期时间,这个也是使用毫秒生成的,使用当前时间+前面传入的持续时间生成
            builder.setExpiration(exp);
        }
        return builder.compact();
    }
}
public class JwtRealm extends AuthorizingRealm {

    private Logger logger = LoggerFactory.getLogger(JwtRealm.class);

    private static JwtUtil jwtUtil = new JwtUtil();

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 授权操作
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 暂时不需要实现
        return null;
    }

    /**
     * 身份认证操作
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String jwt = (String) token.getPrincipal();
        if(jwt == null){
            throw new NullPointerException("jwtToken 不能为空!");
        }
        // 判断
        if(!jwtUtil.isVerify(jwt)){
            throw new UnknownAccountException();
        }
        // 可以获取用户相关信息
        String username = (String) jwtUtil.decode(jwt).get("username");
        logger.info("鉴权用户 username:{}", username);
        return new SimpleAuthenticationInfo(jwt, jwt, "JwtRealm");
    }
}
public class JwtFilter extends AccessControlFilter {

    private Logger logger = LoggerFactory.getLogger(JwtFilter.class);

    /**
     * 判断是否携带有效的JwtToken
     * @param servletRequest
     * @param servletResponse
     * @param o
     * @return
     * @throws Exception
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        return false;
    }

    /**
     * 返回为true表示通过
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        // 从header或者请求参数Param中获取token
        JwtToken jwtToken = new JwtToken(request.getHeader("Authorization")!=null
                ?request.getHeader("Authorization"):request.getParameter("token"));
        try {
            // 鉴权认证
            getSubject(servletRequest, servletResponse).login(jwtToken);
            return true;
        } catch (Exception e) {
            logger.error("鉴权认证失败", e);
            onLoginFail(servletResponse);
            return false;
        }
    }

    /**
     * 鉴权认证失败时默认返回 401 状态码
     */
    private void onLoginFail(ServletResponse response) throws IOException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        httpResponse.getWriter().write("Auth Err!");
    }
}
@Configuration
public class ShiroConfig {

    @Bean
    public SubjectFactory subjectFactory() {
        class JwtDefaultSubjectFactory extends DefaultWebSubjectFactory {
            @Override
            public Subject createSubject(SubjectContext context) {
                context.setSessionCreationEnabled(false);
                return super.createSubject(context);
            }
        }
        return new JwtDefaultSubjectFactory();
    }

    @Bean
    public Realm realm() {
        return new JwtRealm();
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm());
        // 关闭 ShiroDAO 功能
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        // 不需要将 Shiro Session 中的东西存到任何地方(包括 Http Session 中)
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        // 禁止Subject的getSession方法
        securityManager.setSubjectFactory(subjectFactory());
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager());
        shiroFilter.setLoginUrl("/unauthenticated");
        shiroFilter.setUnauthorizedUrl("/unauthorized");
        // 添加jwt过滤器
        Map<String, Filter> filterMap = new HashMap<>();
        // 设置过滤器【anon\logout可以不设置】
        filterMap.put("anon", new AnonymousFilter());
        filterMap.put("jwt", new JwtFilter());
        filterMap.put("logout", new LogoutFilter());
        shiroFilter.setFilters(filterMap);

        // 拦截器,指定方法走哪个拦截器 【login->anon】【logout->logout】【verify->jwt】
        Map<String, String> filterRuleMap = new LinkedHashMap<>();
        filterRuleMap.put("/login", "anon");
        filterRuleMap.put("/logout", "logout");
        filterRuleMap.put("/verify", "jwt");
        shiroFilter.setFilterChainDefinitionMap(filterRuleMap);

        return shiroFilter;
    }
}
server {

		listen       80;
		server_name  localhost;

		# 首页
		index index.html;

		location / {
			root   /usr/share/nginx/html;
			index  index.html index.htm;
		}

		location /api/ {
			auth_request /auth;
			# 鉴权通过后的处理方式
			proxy_pass http://192.168.156.132:8081/success;
		}

		location = /auth {
			# 发送子请求到HTTP服务,验证客户端的凭据,返回响应码
			internal;
			# 设置参数
			set $query '';
			if ($request_uri ~* "[^\?]+\?(.*)$") {
				set $query $1;
			}
			# 验证成功(如果接口返回一个200的码就通过)
			proxy_pass http://192.168.156.132:8081/verify?$query;
			# 发送原始请求
			proxy_pass_request_body off;
			# 清空 Content-Type
			proxy_set_header Content-Type "";
		 }

		error_page 404 /404.html;
			location = /40x.html {
			}

		error_page   500 502 503 504  /50x.html;
		 location = /50x.html {
		}
	}