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

若要使用自定义jwt,添加依赖

<dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>
1
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

# 自定义的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

# 编写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

# 配置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

# 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

# 自定义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

# 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