Shiro
早睡蛋 7/29/2022 JavaShiro技术
# 官网
https://shiro.apache.org/index.html
# 依赖
shiro依赖
<!--shiro权限认证框架-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shirospring</artifactId>
<version>1.9.0</version>
</dependency>
1
2
3
4
5
6
2
3
4
5
6
若要使用自定义jwt,添加依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
1
2
3
4
5
2
3
4
5
# 自定义的SessionId类
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
* 目的: shiro 的 session 管理
* 自定义session规则,实现前后分离,在跨域等情况下使用token方式进行登录验证才需要,否则没必须使用本类。
* shiro默认使用 ServletContainerSessionManager来做 session管理,
* 它是依赖于浏览器的 cookie来维护 session的,调用 storeSessionId方法保存sesionId到cookie中
* 为了支持无状态会话,我们就需要继承 DefaultWebSessionManager
* 自定义生成sessionId则要实现 SessionIdGenerator
* 备注说明:
* @author Administrator
*/
public class ShiroSessionManager extends DefaultWebSessionManager {
private static final String AUTH_TOKEN = "access_token";
public ShiroSessionManager(){
super();
}
/**
* 获取sessionId,原本是根据sessionKey来获取一个sessionId
* 重写的部分多了一个把获取到的token设置到request的部分。
* 这是因为app调用登陆接口的时候,是没有token的,登陆成功后,产生了token,我们把它放到request中,返回结果给客户端的时候,
* 把它从request中取出来,并且传递给客户端,客户端每次带着这个token过来,就相当于是浏览器的cookie的作用,也就能维护会话了
* @param request
* @param response
* @return
*/
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String sessionId = WebUtils.toHttp(request).getParameter(AUTH_TOKEN);
if (StringUtils.isEmpty(sessionId)){
return super.getSessionId(request,response);
}else {
if (WebUtils.isTrue(request,"__cookie")){
Cookie template = getSessionIdCookie();
Cookie cookie = new SimpleCookie(template);
cookie.setValue(sessionId);
cookie.saveTo(WebUtils.toHttp(request),WebUtils.toHttp(response));
}
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID,sessionId);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,Boolean.TRUE);
return sessionId;
}
}
}
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
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
# 自定义的JwtToken
public class JwtToken implements AuthenticationToken {
private final String token;
public JwtToken(String token){
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token; --//返回55555
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 编写Jwt的验证过滤
用于自定义的jwt验证,可以不配置
import com.shiro_demo.common.enums.LoginEnums;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.springframework.http.HttpMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
* @Author 早睡蛋
* @Date 2022/7/18 15:07:53
* @Desc:
*/
public class JwtFilter extends AccessControlFilter {
//设置请求头中需要传递的字段名
protected static final String AUTHORIZATION_HEADER = "Access-Token";
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest req = (HttpServletRequest) request;
// 解决跨域问题
if(HttpMethod.OPTIONS.toString().matches(req.getMethod())) {
return true;
}
if (isLoginAttempt(request, response)) {
//生成jwt token
JwtToken token = new JwtToken(req.getHeader(AUTHORIZATION_HEADER));
//委托给Realm进行验证
try {
//调用登陆会走Realm中的身份验证方法
getSubject(request, response).login(token);
return true;
} catch (Exception e) {
}
}else{
throw new RuntimeException(LoginEnums.LOGIN_FAIL.getMessage());
}
return false;
}
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
String authorization = req.getHeader(AUTHORIZATION_HEADER);
return authorization != null;
}
}
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
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
# 配置shiro跨域
针对OPTIONS请求预处理
import com.alibaba.fastjson.JSONObject;
import com.shiro_demo.common.enums.LoginEnums;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
/**
* @Author 早睡蛋
* @Date 2022/7/18 16:22:07
* @Desc:Shiro跨域预请求处理
*/
public class CORSAuthenticationFilter extends FormAuthenticationFilter {
// 直接过滤可以访问的请求类型
private static final String REQUET_TYPE = "OPTIONS";
public CORSAuthenticationFilter() {
super();
}
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (((HttpServletRequest) request).getMethod().toUpperCase().equals(REQUET_TYPE)) {
return true;
}
return super.isAccessAllowed(request, response, mappedValue);
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletResponse res = (HttpServletResponse)response;
res.setHeader("Access-Control-Allow-Origin", "*");
res.setStatus(HttpServletResponse.SC_OK);
res.setCharacterEncoding("UTF-8");
PrintWriter writer = res.getWriter();
writer.write(JSONObject.toJSONString(LoginEnums.PLEASE_LOGIN.getResult()));
writer.close();
return false;
}
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
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
# shiro过滤器
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @Author 早睡蛋
* @Date 2022/6/12 16:21:55
* @Desc:Shiro统一拦截
*/
public class ShiroFilter {
public static Map<String, String> linkedHashMap(){
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/api/login/**","anon");
filterChainDefinitionMap.put("/exception/**","anon");
filterChainDefinitionMap.put("/test/**","authc");
//swagger2免拦截
filterChainDefinitionMap.put("/swagger-ui.html**", "anon");
filterChainDefinitionMap.put("/v2/api-docs", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/doc.html", "anon");
filterChainDefinitionMap.put("/**","authc");
return filterChainDefinitionMap;
}
}
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
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
# 自定义Realm类
import com.shiro_demo.common.enums.LoginEnums;
import com.shiro_demo.module.dao.UserDao;
import com.shiro_demo.module.entity.po.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
/**
* @Author 早睡蛋
* @Date 2022/6/6 19:48:59
* @Desc:自定义Realm
*/
@Slf4j
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserDao userDao;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("————身份认证方法————");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 从数据库获取对应用户名密码的用户
System.out.println((String) token.getPrincipal());
User userInfo = userDao.getUserByEmail((String) token.getPrincipal());
if (null == userInfo) {
throw new UnknownAccountException (LoginEnums.LOGIN_FAIL.getMessage());
}
String salt = userInfo.getSalt();
String userPwd = new String((char[]) token.getCredentials());
String userPassword = userInfo.getPassword();
String pwd = DigestUtils.md5DigestAsHex((userPwd+salt).getBytes());
if (!pwd.equals(userPassword)) {
throw new AccountException(LoginEnums.LOGIN_FAIL.getMessage());
}
return new SimpleAuthenticationInfo(token.getPrincipal(), userPwd, getName());
}
}
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
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
# shiro总配置类
import com.shiro_demo.shiro.CORSAuthenticationFilter;
import com.shiro_demo.shiro.ShiroFilter;
import com.shiro_demo.shiro.ShiroSessionManager;
import com.shiro_demo.shiro.UserRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
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.LinkedHashMap;
import java.util.Map;
/**
* @Author 早睡蛋
* @Date 2022/6/6 19:46:37
* @Desc:Shiro配置类
*/
@Configuration
public class ShiroConfig {
/**
* 自定义的 shiro session 缓存管理器,用于跨域等情况下使用 token 进行验证,不依赖于sessionId
* @return
*/
@Bean
public SessionManager sessionManager(){
// 将我们继承后重写的shiro session 注册
ShiroSessionManager shiroSession = new ShiroSessionManager();
// 如果后续考虑多tomcat部署应用,可以使用shiro-redis开源插件来做session的控制,或者nginx的负载均衡
shiroSession.setSessionDAO(new EnterpriseCacheSessionDAO());
return shiroSession;
}
/**
* anon:无权限即可访问
* authc:需要认证才能访问
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/exception/login");
Map<String, String> filterChainDefinitionMap = ShiroFilter.linkedHashMap();
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 自定义拦截器限制并发人数
LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
// 统计登录人数:限制同一帐号同时在线的个数
//filtersMap.put("kickout", kickoutSessionControlFilter());
// 自定义跨域前后端分离验证过滤器-自定义token情况
filtersMap.put("corsAuthenticationFilter", new CORSAuthenticationFilter());
shiroFilterFactoryBean.setFilters(filtersMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(userRealm());
defaultWebSecurityManager.setSessionManager(sessionManager());
return defaultWebSecurityManager;
}
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
return userRealm;
}
}
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
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