美文网首页
springboot2+shiro+jwt整合(一)登录认证

springboot2+shiro+jwt整合(一)登录认证

作者: Deam无限 | 来源:发表于2019-02-26 15:27 被阅读5次

    https://blog.csdn.net/qq_23250633/article/details/81063762

    当我们要把服务器做成无状态时(即服务器端不会保存session),这里我们就可以用到JWT。

    为什么使用JWT?

    1.简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快。

    2.自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库。

    3.安全(security): 与简单的JSON相比,XML和XML数字签名会引入复杂的安全漏洞。

    认证原理

    1.用户登陆之后,使用密码对账号进行签名生成并返回token并设置过期时间;

    2.将token保存到本地,并且每次发送请求时都在header上携带token。

    3.shiro过滤器拦截到请求并获取header中的token,并提交到自定义realm的doGetAuthenticationInfo方法。

    4.通过jwt解码获取token中的用户名,从数据库中查询到密码之后根据密码生成jwt效验器并对token进行验证。

    OK,介绍完后上代码。

    JWT

    引入pom

        <!--JWT-->

        <dependency>

        <groupId>com.auth0</groupId>

        <artifactId>java-jwt</artifactId>

        <version>3.4.0</version>

        </dependency>

    首先我们需要自定义一个对象用来包装token。

    JwtToken

        package com.style.orange.shiro;

        import org.apache.shiro.authc.AuthenticationToken;

        /**

        * @author Mr.Li

        * @create 2018-07-12 15:19

        * @desc

        **/

        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;

            }

        }

    再写一个工具类用来进行签名和效验Token

    JwtToken

        package com.style.orange.utils;

        import com.auth0.jwt.JWT;

        import com.auth0.jwt.JWTVerifier;

        import com.auth0.jwt.algorithms.Algorithm;

        import com.auth0.jwt.exceptions.JWTDecodeException;

        import com.auth0.jwt.interfaces.DecodedJWT;

        import java.util.Date;

        /**

        * @author Mr.Li

        * @create 2018-07-12 14:23

        * @desc JWT工具类

        **/

        public class JwtUtil {

            private static final long EXPIRE_TIME = 5 * 60 * 1000;

            /**

            * 校验token是否正确

            *

            * @param token  密钥

            * @param secret 用户的密码

            * @return 是否正确

            */

            public static boolean verify(String token, String username, String secret) {

                try {

                    //根据密码生成JWT效验器

                    Algorithm algorithm = Algorithm.HMAC256(secret);

                    JWTVerifier verifier = JWT.require(algorithm)

                            .withClaim("username", username)

                            .build();

                    //效验TOKEN

                    DecodedJWT jwt = verifier.verify(token);

                    return true;

                } catch (Exception exception) {

                    return false;

                }

            }

            /**

            * 获得token中的信息无需secret解密也能获得

            *

            * @return token中包含的用户名

            */

            public static String getUsername(String token) {

                try {

                    DecodedJWT jwt = JWT.decode(token);

                    return jwt.getClaim("username").asString();

                } catch (JWTDecodeException e) {

                    return null;

                }

            }

            /**

            * 生成签名,5min后过期

            *

            * @param username 用户名

            * @param secret  用户的密码

            * @return 加密的token

            */

            public static String sign(String username, String secret) {

                Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);

                Algorithm algorithm = Algorithm.HMAC256(secret);

                // 附带username信息

                return JWT.create()

                        .withClaim("username", username)

                        .withExpiresAt(date)

                        .sign(algorithm);

            }

        }

    前面认证原理说到我们要使用shiro来拦截token,那就需要我们自己写一个jwt过滤器来作为shiro的过滤器。

    JwtFilter

        import com.style.orange.shiro.JwtToken;

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

        import org.springframework.http.HttpStatus;

        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;

        /**

        * @author Mr.Li

        * @create 2018-07-12 15:56

        * @desc

        **/

        public class JwtFilter extends BasicHttpAuthenticationFilter {

            /**

            * 执行登录认证

            *

            * @param request

            * @param response

            * @param mappedValue

            * @return

            */

            @Override

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

                try {

                    executeLogin(request, response);

                    return true;

                } catch (Exception e) {

                    return false;

                }

            }

            /**

            *

            */

            @Override

            protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {

                HttpServletRequest httpServletRequest = (HttpServletRequest) request;

                String token = httpServletRequest.getHeader("Authorization");

                JwtToken jwtToken = new JwtToken(token);

                // 提交给realm进行登入,如果错误他会抛出异常并被捕获

                getSubject(request, response).login(jwtToken);

                // 如果没有抛出异常则代表登入成功,返回true

                return true;

            }

            /**

            * 对跨域提供支持

            */

            @Override

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

                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"));

                // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态

                if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {

                    httpServletResponse.setStatus(HttpStatus.OK.value());

                    return false;

                }

                return super.preHandle(request, response);

            }

        }

    Shiro

    引入pom

        <!--shiro-->

        <dependency>

        <groupId>org.apache.shiro</groupId>

        <artifactId>shiro-spring</artifactId>

        <version>1.4.0</version>

        </dependency>

    准备自定义Realm

    MyRealm

        package com.style.orange.shiro;

        import com.style.orange.model.SysUser;

        import com.style.orange.service.SysUserService;

        import com.style.orange.utils.JwtUtil;

        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.beans.factory.annotation.Autowired;

        import org.springframework.stereotype.Component;

        /**

        * @author Mr.Li

        * @create 2018-07-12 15:23

        * @desc

        **/

        @Component

        public class MyRealm extends AuthorizingRealm {

            @Autowired

            private SysUserService sysUserService;

            /**

            * 必须重写此方法,不然Shiro会报错

            */

            @Override

            public boolean supports(AuthenticationToken token) {

                return token instanceof JwtToken;

            }

            /**

            * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的

            */

            @Override

            protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

                String username = JwtUtil.getUsername(principals.toString());

                SysUser user = sysUserService.findByUserName(username);

                SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

                return simpleAuthorizationInfo;

            }

            /**

            * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。

            */

            @Override

            protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {

                String token = (String) auth.getCredentials();

                // 解密获得username,用于和数据库进行对比

                String username = JwtUtil.getUsername(token);

                if (username == null) {

                    throw new AuthenticationException("token无效");

                }

                SysUser userBean = sysUserService.findByUserName(username);

                if (userBean == null) {

                    throw new AuthenticationException("用户不存在!");

                }

                if (!JwtUtil.verify(token, username, userBean.getPassword())) {

                    throw new AuthenticationException("用户名或密码错误");

                }

                return new SimpleAuthenticationInfo(token, token, "my_realm");

            }

        }

    ShiroConfig

        package com.style.orange.shiro;

        import com.style.orange.filter.JwtFilter;

        import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;

        import org.apache.shiro.mgt.DefaultSubjectDAO;

        import org.apache.shiro.mgt.SecurityManager;

        import org.apache.shiro.spring.web.ShiroFilterFactoryBean;

        import org.apache.shiro.web.mgt.DefaultWebSecurityManager;

        import org.springframework.context.annotation.Bean;

        import org.springframework.context.annotation.Configuration;

        import javax.servlet.Filter;

        import java.util.HashMap;

        import java.util.LinkedHashMap;

        import java.util.Map;

        /**

        * @author: PENG

        * @date: 2018/2/7

        * @description: shiro 配置类

        */

        @Configuration

        public class ShiroConfig {

            @Bean("shiroFilter")

            public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {

                ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

                shiroFilterFactoryBean.setSecurityManager(securityManager);

                //拦截器

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

                // 配置不会被拦截的链接 顺序判断

                filterChainDefinitionMap.put("/login/**", "anon");

                filterChainDefinitionMap.put("/**.js", "anon");

                filterChainDefinitionMap.put("/druid/**", "anon");

                filterChainDefinitionMap.put("/swagger**/**", "anon");

                filterChainDefinitionMap.put("/webjars/**", "anon");

                filterChainDefinitionMap.put("/v2/**", "anon");

                // 添加自己的过滤器并且取名为jwt

                Map<String, Filter> filterMap = new HashMap<String, Filter>(1);

                filterMap.put("jwt", new JwtFilter());

                shiroFilterFactoryBean.setFilters(filterMap);

                //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边

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

                //未授权界面;

                shiroFilterFactoryBean.setUnauthorizedUrl("/403");

                shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

                return shiroFilterFactoryBean;

            }

            @Bean("securityManager")

            public SecurityManager securityManager(MyRealm myRealm) {

                DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

                securityManager.setRealm(myRealm);

                /*

                * 关闭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);

                return securityManager;

            }

        }

    OK,接下来我们进行测试,我这里集成了swagger2,测试起来比较方便。

    如图红色标注就是返回的token,之后我把token放到请求的header中访问就可以了通过了。

    ---------------------

    作者:小阳style

    来源:CSDN

    原文:https://blog.csdn.net/qq_23250633/article/details/81063762

    版权声明:本文为博主原创文章,转载请附上博文链接!

    相关文章

      网友评论

          本文标题:springboot2+shiro+jwt整合(一)登录认证

          本文链接:https://www.haomeiwen.com/subject/smwayqtx.html