zoukankan      html  css  js  c++  java
  • SpringBoot如何整合JWT+Shiro

    引入相关的依赖

         <!--shiro-redis-->
            <dependency>
                <groupId>org.crazycake</groupId>
                <artifactId>shiro-redis-spring-boot-starter</artifactId>
                <version>3.2.1</version>
            </dependency>

         
            <!--jwt依赖-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.9.1</version>
            </dependency>

    编写配置

    ShiroConfig

    import com.demo.shiro.AccountReaIm;
    import com.demo.shiro.LoginReaIm;
    import com.demo.shiro.JwtFilter;
    import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
    import org.apache.shiro.mgt.DefaultSubjectDAO;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.realm.Realm;
    import org.apache.shiro.session.mgt.SessionManager;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
    import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
    import org.crazycake.shiro.RedisCacheManager;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.crazycake.shiro.RedisSessionDAO;

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

    /***
     * 引入RedisSessionDAO和RedisCacheManager,为了解决shiro的权限数据和会话信息能保存到redis中,实现会话共享。
     */
    @Configuration
    public class ShiroConfig {
        @Autowired
        JwtFilter jwtFilter;
        /**
         * 重建了SessionManager
         *
         * @param redisSessionDAO
         * @return
         */
        @Bean
        public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {
            DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
            // inject redisSessionDAO
            sessionManager.setSessionDAO(redisSessionDAO);
            return sessionManager;

        }

        /**
         * 重建DefaultWebSecurityManager
         * DefaultWebSecurityManager中为了关闭shiro自带的session方式,我们需要设置为false,
         * 这样用户就不再能通过session方式登录shiro。后面将采用jwt凭证登录。
         *
         * @param accountRealm
         * @param sessionManager
         * @param redisCacheManager
         * @return
         */
        @Bean
        public DefaultWebSecurityManager securityManager(LoginReaIm loginReaIm,
                                                         AccountReaIm accountRealm,
                                                         SessionManager sessionManager,
                                                         RedisCacheManager redisCacheManager) {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            //inject sessionManager
            securityManager.setSessionManager(sessionManager);

            // inject redisCacheManager
            securityManager.setCacheManager(redisCacheManager);
            /*
             * 关闭shiro自带的session,详情见文档
             * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
             */
            DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
            DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
            defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
            subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
            securityManager.setSubjectDAO(subjectDAO);
            /**
             * 多个LoginReaIm
             */
            ArrayList<Realm> arrayList=new ArrayList<>();
            arrayList.add(accountRealm);
            arrayList.add(loginReaIm);

            securityManager.setRealms(arrayList);

            return securityManager;
        }

        /**
         * 在ShiroFilterChainDefinition中,我们不再通过编码形式拦截Controller访问路径,
         * 而是所有的路由都需要经过JwtFilter这个过滤器,然后判断请求头中是否含有jwt的信息,有就登录,没有就跳过。
         * 跳过之后,有Controller中的shiro注解进行再次拦截,比如@RequiresAuthentication,这样控制权限访问。
         *
         * @return
         */
        @Bean
        public ShiroFilterChainDefinition shiroFilterChainDefinition() {
            DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
            //拦截器
            Map<String, String> filterMap = new LinkedHashMap<>();
    //        filterMap.put("/test/**", "anon");
             配置不会被拦截的链接 顺序判断
            filterMap.put("/**", "jwt");
            definition.addPathDefinitions(filterMap);
            return definition;
        }

        @Bean("shiroFilterFactoryBean")
        public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
                                                             ShiroFilterChainDefinition shiroFilterChainDefinition) {
            ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
            shiroFilter.setSecurityManager(securityManager);
            // 添加自己的过滤器并且取名为jwt
            Map<String, Filter> filters = new HashMap<>();
            filters.put("jwt",jwtFilter);
            shiroFilter.setFilters(filters);

            Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();

            shiroFilter.setFilterChainDefinitionMap(filterMap);
            return shiroFilter;
        }
    }


    AccountRealm 验证JWT
    AccountRealm是shiro进行登录或者权限校验的逻辑所在,算是核心了,我们需要重写3个方法,分别是

    • supports:为了让realm支持jwt的凭证校验

    • doGetAuthorizationInfo:权限校验

    • doGetAuthenticationInfo:登录认证校验

    @Component
    public class AccountReaIm extends AuthorizingRealm {
        @Autowired
        JwtUtils jwtUtils;
        @Autowired
        TestService service;

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


        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;
        }

        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            JwtToken jwtToken = (JwtToken) authenticationToken;
            String userid = jwtUtils.getClaimByToken((String) jwtToken.getPrincipal()).getSubject();
            Test test=service.selectByPrimaryKey( Integer.parseInt(userid));
            if (test == null) {
                throw new UnknownAccountException("账户不存在");
            }
            login profile = new login();
            BeanUtil.copyProperties(test, profile);
            return new SimpleAuthenticationInfo(profile, jwtToken.getCredentials(), getName());
        }
    }


    LoginReaIm 验证登录
    与上面的区别验证的Token不一样

    @Log4j2
    @Component
    public class LoginReaIm extends AuthorizingRealm {

        @Autowired
        TestService service;
        @Autowired
        JwtUtils jwtUtils;
        /**
         * 必须重写此方法,不然Shiro会报错
         */
        @Override
        public boolean supports(AuthenticationToken token) {
            return token instanceof 
                    UsernamePasswordToken;
        }
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;
        }

        /**
         *shiro 身份验证
         * @param token
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            String userId = token.getPrincipal().toString();
           Test test=service.selectByPrimaryKey( Integer.parseInt(userId));
            if (test == null) {
                throw new UnknownAccountException("账户不存在");
            }
            login profile = new login();
            BeanUtil.copyProperties(test, profile);

            return new SimpleAuthenticationInfo(profile,test.getName(), getName());
        }
    }


    JwtToken


    /**
     * 我们需要重写  AuthenticationToken接口 此接口的作用
     * AuthenticationToken: shiro中负责把username,password生成用于验证的token的封装类
     * 自定义一个对象用来封装token
     */
    public class JwtToken  implements AuthenticationToken {
        private String token;
    public  JwtToken (String token){
        this.token=token;
    }
        @Override
        public Object getPrincipal() {
            return token;
        }

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


    JwtFilter

    import cn.hutool.json.JSONUtil;
    import com.demo.exception.AjaxResponse;
    import com.demo.exception.CustomExceptionType;
    import com.demo.utils.JwtUtils;
    import io.jsonwebtoken.Claims;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.ExpiredCredentialsException;
    import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
    import org.apache.shiro.web.util.WebUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.web.bind.annotation.RequestMethod;

    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;


    @Component
    public class JwtFilter extends AuthenticatingFilter {
        @Autowired
        JwtUtils jwtUtils;

        @Override
        protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            String jwt = request.getHeader("Authorization");
            if (StringUtils.isEmpty(jwt)) {
                return null;
            }
            return new JwtToken(jwt);
        }

        @Override
        protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            String jwt = request.getHeader("Authorization");
            if (StringUtils.isEmpty(jwt)) {
                return true;
            } else {

                //校验Jwt
                Claims claims = jwtUtils.getClaimByToken(jwt);
                if (claims == null || jwtUtils.isTokenExpired(claims.getExpiration())) {
                    throw new ExpiredCredentialsException("token已失效,请重新登录");
                }

                //执行登录
                return executeLogin(servletRequest, servletResponse);
            }

        }

        @Override
        protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            AjaxResponse result = AjaxResponse.error(CustomExceptionType.USER_INPUT_ERROR, throwable.getMessage());
            String json = JSONUtil.toJsonStr(result);
            try {
                httpServletResponse.getWriter().print(json);
            } catch (IOException ioException) {

            }
            return false;
        }

        @Override
        protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {

            HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
            HttpServletResponse httpServletResponse = WebUtils.toHttp(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请求,这里我们给OPTIONS请求直接返回正常状态
            if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
                httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());
                return false;
            }

            return super.preHandle(request, response);
        }
    }


    JwtUtils是个生成和校验jwt的工具类

    /**
     * jwt工具类
     */
    @Slf4j
    @Data
    @Component
    @ConfigurationProperties(prefix = "markerhub.jwt")
    public class JwtUtils {

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

        /**
         * 生成jwt token
         */
        public String generateToken(long userId) {
            Date nowDate = new Date();
            //过期时间
            Date expireDate = new Date(nowDate.getTime() + expire * 1000);

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

        public Claims getClaimByToken(String token) {
            try {
                return Jwts.parser()
                        .setSigningKey(secret)
                        .parseClaimsJws(token)
                        .getBody();
            }catch (Exception e){
                log.debug("validate is token error ", e);
                return null;
            }
        }

        /**
         * token是否过期
         * @return  true:过期
         */
        public boolean isTokenExpired(Date expiration) {
            return expiration.before(new Date());
        }
    }


    基本的校验的路线完成之后,我们需要少量的基本信息配置yml

    markerhub:
        jwt:
          # 加密秘钥
          secret: f4e2e52034348f86b67cde581c0f9eb5
          # token有效时长,7天,单位秒
          expire: 604800
          header: token



    这是我做的一个demo的整合,具体配置还是根据自己的项目进行配置

  • 相关阅读:
    linux下postgresql的c程序编译问题
    Linux下Sublime Text 2中文显示及中文输入问题[转][ubuntu 10.10]
    linux英文斜体乱码 【ubuntu 10.10】
    dropbox无法访问后国内网盘对比选择
    Linux查看系统信息的一些命令及查看已安装软件包的命令(转)
    linux配置ssh 【ubuntu 10.10】
    error: failed to push some refs【Linux】【Git】
    一些曾经收藏的话
    Linux系统源码安装过程中的prefix选项【转】
    LINUX GBK>UTF8文件编码批量转换脚本[转]
  • 原文地址:https://www.cnblogs.com/gqzdev/p/13823786.html
Copyright © 2011-2022 走看看