IT俱乐部 Java springboot+shiro+jwtsession和token进行身份验证和授权

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

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

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

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

pom加依赖

1
org.crazycakeshiro-redis2.4.2.1-RELEASEorg.apache.shiroshiro-all1.3.2com.auth0java-jwt3.2.0

1.JwtToken重写token类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
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 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 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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 filterMap = new HashMap();
        filterMap.put("jwt",jwtFilter);
        shiroFilterFactoryBean.setFilters(filterMap);
        Map 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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 claimMap = JwtUtils.verifyToken(token);
        String username = claimMap.get("name").asString();
        Map 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工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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 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 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 getClaims(String token) throws UnsupportedEncodingException {
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build();
 
        DecodedJWT jwt = null;
 
        jwt = verifier.verify(token);
 
        return jwt.getClaims();
    }
 
 
}

7.密码验证器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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 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应用。

本文收集自网络,不代表IT俱乐部立场,转载请注明出处。https://www.2it.club/code/java/12378.html
上一篇
下一篇
联系我们

联系我们

在线咨询: QQ交谈

邮箱: 1120393934@qq.com

工作时间:周一至周五,9:00-17:30,节假日休息

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部