java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > springboot+shiro+jwt兼容session和token使用

springboot+shiro+jwtsession和token进行身份验证和授权

作者:疯狂迪霸戈

最近和别的软件集成项目,需要提供给别人接口来进行数据传输,发现给他token后并不能访问我的接口,拿postman试了下还真是不行,检查代码发现项目的shiro配置是通过session会话来校验信息的,修改代码兼容token和session

最近和别的软件集成项目,需要提供给别人接口来进行数据传输,发现给他token后并不能访问我的接口,拿postman试了下还真是不行。检查代码发现项目的shiro配置是通过session会话来校验信息的 ,我之前一直是前后端自己写,用浏览器来调试的程序所以没发现这个问题。

浏览器请求头的cookie带着JESSIONID是可以正常访问接口的

那要和别的项目集成,他那边又不是通过浏览器,咋办呢,我这边改造吧,兼容token和session不就行了,下面直接贴改造后的完整代码。

pom加依赖

<dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>2.4.2.1-RELEASE</version>
        </dependency>
             <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-all</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>

1.JwtToken重写token类型

package com.mes.common.token;

import com.mes.module.user.dto.SysUserDto;
import lombok.Data;
import org.apache.shiro.authc.HostAuthenticationToken;
import org.apache.shiro.authc.RememberMeAuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;


@Data
public class JwtToken implements HostAuthenticationToken, RememberMeAuthenticationToken {
    private String token;
    private char[] password;
    private boolean rememberMe = false;
    private String host;

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


    @Override
    public String getHost() {
        return null;
    }

    @Override
    public boolean isRememberMe() {
        return false;
    }

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

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

2.自定义过滤器 JwtFilter

package com.mes.common.shiro;

import com.alibaba.fastjson.JSON;
import com.auth0.jwt.interfaces.Claim;
import com.mes.common.token.JwtToken;
import com.mes.common.utils.JwtUtils;
import com.mes.common.utils.Result;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;

/**
 * @Description 自定义过滤器
 * @Date 2021/8/18
 **/
public class JwtFilter extends AuthenticatingFilter {

    private static final Logger log = LoggerFactory.getLogger(JwtFilter.class);
    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String token = request.getHeader("token");
        if (token == null){
            return null;
        }
        return new JwtToken(token);
    }


    /**
     * 拦截校验  没有登录的情况下会走此方法
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String token = request.getHeader("token");

        response.setContentType("application/json;charset=utf-8");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Methods", "GET,PUT,DELETE,UPDATE,OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));

        Subject subject = getSubject(servletRequest,servletResponse);

        if (!subject.isAuthenticated()){
            // 未登录
            PrintWriter writer = response.getWriter();
            writer.print(JSON.toJSONString(new Result<>().setCode(402).setMsg("请先登录")));
            return false;
        }

        if (StringUtils.isEmpty(token)){
            PrintWriter writer = response.getWriter();
            writer.print(JSON.toJSONString(new Result<>().setCode(402).setMsg("请先登录")));
            return false;
        }else {
            // 校验jwt
            try {
                Map<String, Claim> claimMap = JwtUtils.verifyToken(token);
            } catch (Exception e) {
                e.printStackTrace();
                PrintWriter writer = response.getWriter();
                writer.write(JSON.toJSONString(new Result<>().setCode(402).setMsg("登录失效,请重新登录")));
                return false;
            }
            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();

        Result result = new Result().err().setMsg(e.getMessage());
        String json = JSON.toJSONString(result);

        try {
            httpServletResponse.getWriter().print(json);
        } catch (IOException ioException) {

        }
        return false;
    }

    /**
     * 跨域支持
     * @param servletRequest
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest servletRequest, ServletResponse response) throws Exception {
        HttpServletRequest httpRequest = WebUtils.toHttp(servletRequest);
        HttpServletResponse httpResponse = WebUtils.toHttp(response);
        if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-control-Allow-Origin", httpRequest.getHeader("Origin"));
            httpResponse.setHeader("Access-Control-Allow-Methods", "GET,PUT,DELETE,UPDATE,OPTIONS");
            httpResponse.setHeader("Access-Control-Allow-Headers", httpRequest.getHeader("Access-Control-Request-Headers"));

            System.out.println(httpRequest.getHeader("Origin"));
            System.out.println(httpRequest.getMethod());
            System.out.println(httpRequest.getHeader("Access-Control-Request-Headers"));
            httpResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String token = request.getHeader("token");
        if (token != null) {
            try {
//                Map<String, Claim> claimMap = JwtUtils.verifyToken(token);
//                String authToken = claimMap.get("token").asString();
                JwtToken jwtToken = new JwtToken(token);
                Subject subject = SecurityUtils.getSubject();
                subject.login(jwtToken);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                log.error("token失效,请重新登录");
               response.getWriter().print(JSON.toJSONString(new Result<>().setCode(402).setMsg("token失效,请重新登录")));
            }
            return false;
        }else {
            // session方式
            return super.preHandle(servletRequest, response);
        }
    }

/*    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpRequest = WebUtils.toHttp(request);
        HttpServletResponse httpResponse = WebUtils.toHttp(response);
        if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-control-Allow-Origin", httpRequest.getHeader("Origin"));
            httpResponse.setHeader("Access-Control-Allow-Methods", "GET,PUT,DELETE,UPDATE,OPTIONS");
            httpResponse.setHeader("Access-Control-Allow-Headers", httpRequest.getHeader("Access-Control-Request-Headers"));

            System.out.println(httpRequest.getHeader("Origin"));
            System.out.println(httpRequest.getMethod());
            System.out.println(httpRequest.getHeader("Access-Control-Request-Headers"));
            httpResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }*/

}

3.配置过滤器 ShiroFilterRegisterConfig

package com.mes.common.config;

import com.mes.common.shiro.JwtFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Description TODO
 * @Date 2021/8/19
 **/
@Configuration
public class ShiroFilterRegisterConfig {
    @Bean
    public FilterRegistrationBean shiroLoginFilteRegistration(JwtFilter filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean(filter);
        registration.setEnabled(false);
        return registration;
    }
}

4. shiroConfig

package com..mes.common.config;

import com.baomidou.mybatisplus.extension.api.R;
import com..mes.common.constant.ExpTime;
import com..mes.common.realm.MyRealm;
import com..mes.common.shiro.JwtFilter;
import com..mes.common.shiro.MyCredentialsMatcher;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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;

/**
 * @Description shiro配置
 * @Date 2021/8/18
 **/
@Configuration
public class ShiroConfig {
    @Autowired
    private MyRealm myRealm;
    @Autowired
    private MyCredentialsMatcher myCredentialsMatcher;
    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.port}")
    private Integer redisPort;
    @Value("${spring.redis.timeout}")
    private Integer redisTimeout;

//    @Bean
//    public DefaultWebSessionManager sessionManager(@Value("${globalSessionTimeout:3600}") long globalSessionTimeout, RedisManager c){
//        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//        sessionManager.setSessionValidationSchedulerEnabled(true);
//        sessionManager.setSessionIdUrlRewritingEnabled(false);
//        sessionManager.setSessionValidationInterval(globalSessionTimeout * 1000);
//        sessionManager.setGlobalSessionTimeout(globalSessionTimeout * 1000);
//        sessionManager.setSessionDAO(redisSessionDAO(c));
//        return sessionManager;
//    }
//    @ConfigurationProperties(prefix="spring.redis")
//    @Bean
//    public RedisManager redisManager() {
//        return new RedisManager();
//    }
//    @Bean
//    public RedisSessionDAO redisSessionDAO(RedisManager redisManager) {
//        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
//        redisSessionDAO.setRedisManager(redisManager);
//        return redisSessionDAO;
//    }


//    @Bean
//    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
//
//        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
//        defaultAdvisorAutoProxyCreator.setUsePrefix(true);
//
//        return defaultAdvisorAutoProxyCreator;
//    }






    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(SessionManager sessionManager, RedisCacheManager redisCacheManager){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        myRealm.setCredentialsMatcher(myCredentialsMatcher);
        defaultWebSecurityManager.setRealm(myRealm);
        defaultWebSecurityManager.setSessionManager(sessionManager);
        defaultWebSecurityManager.setCacheManager(redisCacheManager);
        return defaultWebSecurityManager;
    }

    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager,JwtFilter jwtFilter){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//        JwtFilter jwtFilter = new JwtFilter();
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("jwt",jwtFilter);
        shiroFilterFactoryBean.setFilters(filterMap);
        Map<String,String> map = new LinkedHashMap<>();
        map.put("/sys/user/login","anon");
        map.put("/swagger-ui.html**", "anon");
        map.put("/v2/api-docs", "anon");
        map.put("/swagger-resources/**", "anon");
        map.put("/webjars/**", "anon");
        map.put("/img/**","anon");
        map.put("/fastdfs/**","anon");
        map.put("/**","jwt");  //取消就不会拦截
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
//        shiroFilterFactoryBean.setLoginUrl("http://192.168.18.17:3000");
        return shiroFilterFactoryBean;
    }



    @Bean
    public JwtFilter getJwtFilter(){
        return new JwtFilter();
    }



    /**
     * 配置shiro redisManager
     * 使用的是shiro-redis开源插件
     * @return
     */
    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(redisHost);
        redisManager.setPort(redisPort);
        redisManager.setExpire(Math.toIntExact(ExpTime.expTime));// 配置缓存过期时间
        redisManager.setTimeout(redisTimeout);
        return redisManager;
    }
    @Bean
    public RedisSessionDAO redisSessionDAO(RedisManager redisManager) {
//        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager);
        return redisSessionDAO;
    }
    /**
     * shiro session的管理
     */
    @Bean
    public DefaultWebSessionManager redisSessionManager(RedisSessionDAO redisSessionDAO) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO);
        return sessionManager;
    }
    @Bean
    public RedisCacheManager redisCacheManager(RedisManager redisManager) {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager);
        return redisCacheManager;
    }

//    @Bean
//    public FilterRegistrationBean shiroLoginFilteRegistration(JwtFilter filter) {
//        FilterRegistrationBean registration = new FilterRegistrationBean(filter);
//        registration.setEnabled(false);
//        return registration;
//    }

}

5.自定义认证逻辑 MyRealm

package com.mes.common.realm;

import com.auth0.jwt.interfaces.Claim;
import com.mes.common.token.JwtToken;
import com.mes.common.utils.JwtUtils;
import com.mes.module.user.dto.SysUserDto;
import com.mes.module.user.service.SysUserService;
import lombok.SneakyThrows;
import org.apache.shiro.authc.*;
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;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description 授权
 * @Date 2021/8/18
 **/
@Component
public class MyRealm extends AuthorizingRealm {
    @Autowired
    private SysUserService sysUserService;

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

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String username = (String) principalCollection.iterator().next();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        return info;
    }

    @SneakyThrows
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        JwtToken jwtToken = (JwtToken) authenticationToken;
        String token = (String) jwtToken.getPrincipal();
        Map<String, Claim> claimMap = JwtUtils.verifyToken(token);
        String username = claimMap.get("name").asString();
        Map<String,Object> params = new HashMap<>();
        params.put("username", username);
        SysUserDto userDto = sysUserService.getOne(params);
        if (userDto == null){
            return null;
        }

//        return new SimpleAuthenticationInfo(userDto,userDto.getPassword(),getName());
        return new SimpleAuthenticationInfo(userDto,jwtToken,getName());
    }
}

6. token工具类

package com.mes.common.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.mes.common.constant.ExpTime;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import lombok.Data;

import javax.xml.bind.DatatypeConverter;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


@Data
public class JwtUtils {
    /**
     * 加密的秘钥,相当于服务器私钥,一定保管好,不能泄露
     */
    private static final String secret = "secret";
    /**
     * token的有效时间,不需要自己验证失效,当失效后,会自动抛出异常
     */
    public static final Long expTime = ExpTime.expTime;

    public static String createToken(long id, String name, long loginId) throws UnsupportedEncodingException {

        Map<String, Object> map = new HashMap<>();
        map.put("alg", "HS256");
        map.put("typ", "JWT");

        String token = JWT.create()
                .withHeader(map)
                .withClaim("id", id)
                .withClaim("name", name)
                .withClaim("loginId", loginId)
                .withExpiresAt(new Date(System.currentTimeMillis() + expTime))
                .withIssuedAt(new Date())
                .sign(Algorithm.HMAC256(secret));

        return token;
    }

    public static Map<String, Claim> verifyToken(String token) throws UnsupportedEncodingException {
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build();


        DecodedJWT jwt = null;

        try {
            jwt = verifier.verify(token);
        } catch (Exception e) {
            throw new RuntimeException("登录凭证已过期,请重新登录");
        }
        return jwt.getClaims();
    }

    public static Map<String, Claim> getClaims(String token) throws UnsupportedEncodingException {
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build();

        DecodedJWT jwt = null;

        jwt = verifier.verify(token);

        return jwt.getClaims();
    }


}

7.密码验证器

package com.mes.common.shiro;

import com.mes.common.token.JwtToken;
import com.mes.common.utils.CommonsUtils;
import com.mes.module.user.dto.SysUserDto;
import com.mes.module.user.service.SysUserService;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description 密码验证器
 * @Date 2021/8/18
 **/
@Component
public class MyCredentialsMatcher extends SimpleCredentialsMatcher {
    @Autowired
    private SysUserService sysUserService;

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {

        JwtToken jwtToken = (JwtToken) token;
        if (jwtToken.getPassword() == null){
            return true;
        }
        String inPassword = new String(jwtToken.getPassword());
        SysUserDto dto = (SysUserDto) info.getPrincipals();
        String username = dto.getUsername();
        String dbPassword = String.valueOf(info.getCredentials());
        Map<String,Object> params = new HashMap<>();
        params.put("username",username);
        SysUserDto dbUser = sysUserService.getOne(params);
        String salt = dbUser.getSalt();
        if (CommonsUtils.encryptPassword(inPassword,salt).equals(dbPassword)){
            return true;
        }else {
            return false;
        }


    }
}

8.总结

在Spring Boot, Shiro和JWT的项目中,可以同时使用session和token进行身份验证和授权,但通常token用于无状态的RESTful API,而session用于长连接的情况,如Web应用。

您可能感兴趣的文章:
阅读全文