Shiro&Jwt验证

此篇基于 SpringBoot 整合 Shiro & Jwt 进行鉴权 相关代码编写及解析

首先我们创建 JwtFilter 类 继承自 BasicHttpAuthenticationFilter

org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

此类是一个过滤器,后期会通过Shiro配置进去

重写4个重要的方法 其执行顺序亦是如下

1. preHandle(..)  前置处理
2. isAccessAllowed(..) 请求方法是否被允许
3. isLoginAttempt(..) 是否是登陆请求,去查看请求头里是否包含Authorization请求头
4. executeLogin(..) 执行登陆操作 其会调用getSubject(request, response).login(jwtToken)进行登陆验权

preHandle 方法

可以理解为前置处理,我们在这进行一些跨域必要设置

HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
//跨域请求会发送两次请求首次为预检请求,其方法为 OPTIONS
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
     httpServletResponse.setStatus(HttpStatus.OK.value());
     return false;
}
return super.preHandle(request, response);

isAccessAllowed 方法

其实这个方法我们会手动调用 isLoginAttempt 方法及 executeLogin 方法
isLoginAttempt判断用户是否想要登陆,判断依据为请求头中是否包含 Authorization 授权信息,也就是所谓的 Token
如果有则再执行executeLogin方法进行登陆验证操作,此方法在这里是验证JwtToken是否合法,不合法则返回401需要重新登陆

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {

        if (this.isLoginAttempt(request, response)) {
            try {
                // 进行验证登陆JWT
                this.executeLogin(request, response);
            } catch (Exception e) {

                String msg = e.getMessage();

                Throwable throwable = e.getCause();
                if (throwable instanceof SignatureVerificationException) {
                    // 该异常为JWT的AccessToken认证失败(Token或者密钥不正确)
                    msg = "Token或者密钥不正确(" + throwable.getMessage() + ")";
                } else if (throwable instanceof TokenExpiredException) {

                    if (this.refreshToken(request, response)) {
                        return true;
                    } else {
                        msg = "Token已过期(" + throwable.getMessage() + ")";
                    }
                } else {
                    // 应用异常不为空
                    if (throwable != null) {
                        // 获取应用异常msg
                        msg = throwable.getMessage();
                    }
                }
                HttpServletResponse httpServletResponse = (HttpServletResponse) response;
                httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
                httpServletResponse.setCharacterEncoding("UTF-8");
                httpServletResponse.setContentType("application/json; charset=utf-8");
                try (PrintWriter out = httpServletResponse.getWriter()) {

                    out.append("用户认证失败" + msg);
                } catch (IOException e) {
                    //logger.error("直接返回Response信息出现IOException异常", e);
                }
                return false;
            }
        } else {
            // 没有携带Token
            HttpServletRequest httpRequest = WebUtils.toHttp(request);
            String httpMethod = httpRequest.getMethod();
            String requestURI = httpRequest.getRequestURI();
            logger.info("当前请求 {} Authorization属性(Token)为空 请求类型 {}", requestURI, httpMethod);
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
                httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
                httpServletResponse.setCharacterEncoding("UTF-8");
                httpServletResponse.setContentType("application/json; charset=utf-8");
                try (PrintWriter out = httpServletResponse.getWriter()) {

                    out.append("用户认证失败" + msg);
                } catch (IOException e) {
                    //logger.error("直接返回Response信息出现IOException异常", e);
                }
            return false;
        }
        return true;
    }

isLoginAttempt 方法 是否尝试登陆

    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        String token = this.getAuthzHeader(request);
        return token != null;
    }

executeLogin 方法

执行登陆操作 其实就是对 Token 进行验证操作,这里我们需要另外一个类去处理 Token 验证 (MyRealm 类的doGetAuthenticationInfo方法)

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        String token = this.getAuthzHeader(request);
        //这里需要自己实现对Token验证操作
        JwtToken jwtToken = new JwtToken(token);
        getSubject(request, response).login(jwtToken);
        return true;
    }

创建 JwtToken 类 继承自AuthenticationToken

org.apache.shiro.authc.AuthenticationToken

这里本来是存取用户名及密码的字段,现在因为是Token不存在用户名的问题所以把字段都设置成Token

public class JwtToken implements AuthenticationToken {
    private static final long serialVersionUID = -634556778977L;

    private String token;

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

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

    @Override
    public Object getCredentials() {
        return token;
    }
}

JwtConfig类的创建

这个类用于创建 Token 及解码 Token 里的信息

@ConfigurationProperties(prefix = "config.jwt")
@Component
public class JwtConfig {

    private String secret;
    private long expire;
    private String header;

    /**
     * 生成token
     *
     * @param subject
     * @return
     */
    public String createToken(String subject) {
        Date nowDate = new Date();
        //过期时间
        Date expireDate = new Date(nowDate.getTime() + expire * 1000 * 60 * 60 * 24);

        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(subject)
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 获取token中注册信息
     *
     * @param token
     * @return
     */
    public Claims getTokenClaim(String token) {
        try {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 获取token 解析信息
     * @param token
     * @return
     */
    public String getTokenInfo(String token){
        Claims claims=getTokenClaim(token);
        if(claims==null){
            throw new RuntimeException("Token 信息异常");
        }
        String tokenInfo=claims.getSubject();
        if(StringUtils.isBlank(tokenInfo)){
            throw new RuntimeException("Token 信息异常 解析值为空");
        }
        return tokenInfo;
    }

    /**
     * 验证token是否过期失效
     *
     * @param expirationTime
     * @return
     */
    public boolean isTokenExpired(Date expirationTime) {
        return expirationTime.before(new Date());
    }

    /**
     * 获取token失效时间
     *
     * @param token
     * @return
     */
    public Date getExpirationDateFromToken(String token) {
        return getTokenClaim(token).getExpiration();
    }

    /**
     * 获取用户名从token中
     */
    public String getUsernameFromToken(String token) {
        return getTokenClaim(token).getSubject();
    }

    /**
     * 获取jwt发布时间
     */
    public Date getIssuedAtDateFromToken(String token) {
        return getTokenClaim(token).getIssuedAt();
    }

    ...set get
}

配置文件信息

config.jwt.secret=abc321&%!
config.jwt.expire=15
config.jwt.header=Authorization

实现自己的Realm 创建MyRealm类 继承自AuthorizingRealm

import io.jsonwebtoken.Claims;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author zy
 */
@Component
public class MyRealm extends AuthorizingRealm {

    private static Logger logger = LogManager.getLogger(MyRealm.class);

    /**
    *   这里需要实现自己的用户登陆验证信息及 数据权限相关的信息获取
    */
    @Resource
    private UserService userService;

    @Resource
    private JwtConfig jwtConfig;

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

    /**
     * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        logger.info("====================数据权限认证====================");
        String username = jwtConfig.getTokenInfo(principals.toString());
        UserInfo user = userService.getUserAndRole(username);

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        List<Role> roles = user.getRoles();

        if (roles.isEmpty()) {
            logger.warn("该用户 {} 没有角色,默认赋予user角色", username);
            Role r = new Role();
            r.setRole("user");
            roles.add(r);
        }

        /**
        *    这里是我自己实现的数据权限认证可做参考用
        */
        Set<String> permissionSet = new HashSet<>(roles.size() * 16);
        for (Role role : roles) {
            List<Permission> temList = role.getPermissions();
            if (temList == null || temList.isEmpty()) {
                logger.warn("该角色 {} 没有赋予相应权限信息", role.getRole());
                continue;
            }
            for (Permission tem : temList) {
                if (tem.getId().indexOf(":") > -1) {
                    permissionSet.add(tem.getId());
                }
            }
        }

        simpleAuthorizationInfo.setRoles(roles.stream().map(Role::getRole).collect(Collectors.toSet()));
        simpleAuthorizationInfo.setStringPermissions(permissionSet);
        return simpleAuthorizationInfo;
    }

    /**
     * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) {
        logger.info("====================Token认证====================");
        String token = auth.getCredentials().toString();
        Claims claims = jwtConfig.getTokenClaim(token);
        if (claims == null) {
            throw new AuthenticationException("解析Token异常");
        }
        if (jwtConfig.isTokenExpired(claims.getExpiration())) {
            throw new AuthenticationException("Token过期");
        }
        String username = claims.getSubject();
        if (username == null || username == "") {
            logger.error("Token中帐号为空");
            throw new AuthenticationException("Token中帐号为空");
        }
        UserInfo user = userService.getUserByName(username);
        if (user == null) {
            throw new AuthenticationException("该帐号不存在");
        }
        DataContextHolder.setCurrentUser(user);
        return new SimpleAuthenticationInfo(token, token, getName());
    }
}

配置Shiro 创建ShiroConfig类

LifecycleBeanPostProcessor这个类并不一定要手动创建,手动创建可能存在一些问题。我遇见的坑就在这里。至于原因希望大家不吝赐教

import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author zy
 */
@Configuration
public class ShiroConfig {

    @Bean("securityManager")
    public DefaultWebSecurityManager getManager(MyRealm myRealm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm);

        // 关闭Shiro自带的session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        manager.setSubjectDAO(subjectDAO);

        // 设置自定义Cache缓存 根据项目情况而设置
        manager.setCacheManager(new CustomCacheManager());
        return manager;
    }

    /**
     * 添加自己的过滤器,自定义url规则
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager, RedisTemplate<String, String> redisTemplate, JwtConfig jwtConfig) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

        //配置过滤器 对 『anon』不进行拦截
        Map<String, Filter> filterMap = new HashMap<>(3);
        filterMap.put("anon", new AnonymousFilter());
        filterMap.put("jwt", new JwtFilter(redisTemplate, jwtConfig));

        factoryBean.setFilters(filterMap);
        factoryBean.setSecurityManager(securityManager);

        factoryBean.setLoginUrl("/login");

        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(16);

        // 配置不过滤
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/testpage", "anon");
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/login/**", "anon");
        filterChainDefinitionMap.put("/unauthorized/**", "anon");
        // swagger
        filterChainDefinitionMap.put("/swagger**/**", "anon");
        filterChainDefinitionMap.put("/v2/**", "anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");

        filterChainDefinitionMap.put("/**", "jwt");

        factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return factoryBean;
    }

//    @Bean
//    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
//        return new LifecycleBeanPostProcessor();
//    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

原文地址:https://www.cnblogs.com/dadiwm321/p/shiro_jwt.html

时间: 2024-11-02 16:40:09

Shiro&Jwt验证的相关文章

SpringBoot2.0+Shiro+JWT 整合

SpringBoot2.0+Shiro+JWT 整合 JSON Web Token(JWT)是一个非常轻巧的规范.这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息. 我们利用一定的编码生成 Token,并在 Token 中加入一些非敏感信息,将其传递. 安装环境 开发工具:STS Maven版本:apache-maven-3.5.2 java jdk 1.8 MySQL版本:5.7 系统:Windows10 一.新建Maven项目 配置Maven项目的pom.xml文件 <pr

SpringMVC+Apache Shiro+JPA(hibernate)案例教学(三)给Shiro登录验证加上验证码

序: 给Shiro加入验证码,有多种方式,当然你也可以通过继承修改FormAuthenticationFilter类,通过Shiro去验证验证码.具体实现请百度: 应用Shiro到Web Application(验证码实现) 而今天我要说的,既然使用的SpringMVC,为什么不直接在Controller中就处理验证码验证,让事情变的更简单一点呢? 一.新建ValidateCode.java验证码工具类 package org.shiro.demo.util; import java.util.

Sping Boot + Spring Security + Mybaits + Logback + JWT验证项目开发框架搭建

简介 本文介绍Sping Boot + Spring Security + Mybaits + Logback 项目开发框架搭建过程,并且实现JWT验证,代码已上传到github,地址在文章最后可以直接下载代码. 搭建过程 1 建立工程 工程目录如下: 2 上传至GitHub 使用Git shell 进入文件夹 添加远程仓库 上传到远程仓库 如果提示这个错误 Permission denied(publickey) 需要将本地ssh key添加到仓库 首先产生本地sshkey 将本地sshkey

webapi中使用token验证(JWT验证)

本文介绍如何在webapi中使用JWT验证 准备 安装JWT安装包 System.IdentityModel.Tokens.Jwt 你的前端api登录请求的方法,参考 axios.get("api/token?username=cuong&password=1").then(function (res) { // 返回一个token /* token示例如下 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZ

shiro身份验证

身份验证,即在应用中谁能证明他就是他本人.一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明. 在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份: principals:身份,即主体的标识属性,可以是任何东西,如用户名.邮箱等,唯一即可.一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号. credentials:证明

Shiro 学习笔记(二)——shiro身份验证

身份验证: 在应用中证明他就是他本人.一般上用身份证.用户/密码 来证明. 在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份 Principals: 身份,即主体的标识属性,只要是主体的

(二)Shiro身份验证

身份验证,即在应用中谁能证明他就是他本人.一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明.在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份: principals:身份,即主体的标识属性,可以是任何东西,如用户名.邮箱等,唯一即可.一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号. credentials:证明/

Shiro权限验证代码记录,正确找到shiro框架在什么地方做了权限识别

权限验证方式的验证代码: org.apache.shiro.web.servlet.AdviceFilter这个类是所有shiro框架提供的默认权限验证实例类的父类 验证代码: public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { Exception exception = nu

spring boot + shiro 登录验证

form提交 <form th:action="@{/login}" method="POST"> <div class="form-group has-feedback"> <input name="username" type="text" class="form-control" placeholder="用户账户" require