SpringBoot+Shiro+Redis

2021-01-07  本文已影响0人  虾米咬小米

1、实现共享Session

1、重新设置 session 及 cookie 去除 httpOnly 浏览器 脚本 都能取到 cookie

最终采用:

package com.daoshu.involved.shared.core.shiro;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
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.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;


@Configuration
@Slf4j
public class ShiroConfig {

    private static final int redis_expire = 1000 * 60 * 60 * 2;

    private static final long session_expire = 1000 * 60 * 60 * 24;


    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.port}")
    private String redisPort;

    /**
     * 设置过滤规则
     *
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);


        /**
         * bean.setLoginUrl("/login") 是在项目启动后,如果没有登录的情况下,会被shiro强制请求的路径,即为/unauth ;
         */
        shiroFilterFactoryBean.setLoginUrl("/auth/unauth");

        /**
         * authc,表示拦截的路径,anon,表示不拦截的路径
         * 注意此处使用的是LinkedHashMap,是有顺序的,shiro会按从上到下的顺序匹配验证,匹配了就不再继续验证
         * 所以上面的url要苛刻,宽松的url要放在下面,尤其是"/**"要放到最下面,如果放前面的话其后的验证规则就没作用了。
         */

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        /**
         * swagger 免验证
         */
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/swagger-resources", "anon");
        filterChainDefinitionMap.put("/swagger-resources/**", "anon");
        filterChainDefinitionMap.put("/v2/api-docs", "anon");
        filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/**", "anon");

        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/auth/login", "anon");
        filterChainDefinitionMap.put("/**.jpg", "anon");
        filterChainDefinitionMap.put("/**.ico", "anon");
        filterChainDefinitionMap.put("/**", "authc");


        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }



 /**
     * 
     * 
     *  可以分别采用  单例,及集群
     * RedisClusterManager   redis 集群管理
     * RedisManager   redis 集群管理
     * 
     * @return
     */
    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setDatabase(2);
        redisManager.setHost(redisHost + ":" + redisPort);
        return redisManager;
    }

    /**
     * 基于Redis实现共享Session
     * @return
     */
    @Bean
    public RedisSessionDAO redisSessionDAO(RedisManager redisManager) {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager);
        //  Session ID 生成器
        redisSessionDAO.setSessionIdGenerator(new JavaUuidSessionIdGenerator());
        return redisSessionDAO;
    }

    @Bean
    public SimpleCookie cookie() {
        //  cookie的name,对应的默认是 JSESSIONID
        SimpleCookie cookie = new SimpleCookie("SHAREJSESSIONID");
        /**
         * 浏览器 脚本  都能取到 cookie
         */
        cookie.setHttpOnly(false);
        //  path为 / 用于多个系统共享JSESSIONID
        cookie.setPath("/");
        return cookie;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisManager redisManager) {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager);
        // 设置redis超时
        redisCacheManager.setExpire(redis_expire);
        redisCacheManager.setPrincipalIdFieldName("userId");
        return redisCacheManager;
    }

    /**
     * 凭证匹配器
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //散列的次数
        hashedCredentialsMatcher.setHashIterations(1);
        return hashedCredentialsMatcher;
    }

    /**
     * 自定义realm
     *
     * @return
     */
    @Bean
    public UserRealm userRealm() {
        UserRealm userRealm = new UserRealm();
        userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return userRealm;
    }

    /**
     * 安全管理器
     * 注:使用shiro-spring-boot-starter 1.4时,返回类型是SecurityManager会报错,直接引用shiro-spring则不报错
     *
     * @return
     */
    @Bean
    public SecurityManager securityManager(RedisSessionDAO redisSessionDAO, RedisCacheManager redisCacheManager) {


        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        // 设置session超时
        sessionManager.setGlobalSessionTimeout(session_expire);
        // 删除无效session
        sessionManager.setDeleteInvalidSessions(true);
        // 设置JSESSIONID
        sessionManager.setSessionIdCookie(cookie());
        // 设置sessionDAO
        sessionManager.setSessionDAO(redisSessionDAO);

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setSessionManager(sessionManager);
        securityManager.setCacheManager(redisCacheManager);
        securityManager.setRememberMeManager(cookieRememberMeManager());
        securityManager.setRealm(userRealm());
        return securityManager;
    }








    /**
     * DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理
     *
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
        proxyCreator.setProxyTargetClass(true);
        return proxyCreator;
    }

    /**
     *    开启shiro aop注解支持.
     *    使用代理方式;所以需要开启代码支持;
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    @Bean
    public SimpleMappingExceptionResolver resolver() {
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "error/403");
        exceptionResolver.setExceptionMappings(properties);
        return exceptionResolver;
    }

    @Bean
    public SimpleCookie rememberMeCookie() {
        log.info("ShiroConfiguration.rememberMeCookie()");
        //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");

        //<!-- 记住我cookie生效时间 ,单位秒;-->
        simpleCookie.setMaxAge(1800);
        return simpleCookie;
    }

    @Bean
    public CookieRememberMeManager cookieRememberMeManager() {
        log.info("ShiroConfiguration.rememberMeManager()");
        CookieRememberMeManager manager = new CookieRememberMeManager();
        manager.setCookie(rememberMeCookie());
        return manager;
    }







}

项目发布到微服务k8s里,发现容器数量为1的时候 能正常登录,而当容器数量调整到多个的时候就会发现登录不了。 经排查是多个容器的时候Session会话没保持,就需要在多个应用的时候共享session会话。

上一篇2020-04-01-Shiro Session集群共享存入Redis中SimpleSession的transient 属性不能序列化已经提到了集成redis实现共享session的坑!! 这里我就不用自己去 RedisManager、SessionDAO了,而是使用shiro-redis 框架。

下面通过实现一个小Demo,来说明如何使用并集成shiro-redis!

一、实现步骤

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-all -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-all</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.crazycake/shiro-redis -->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.2.3</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

package com.example.demo.conf;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource("classpath:conf/redis.properties")
public class RedisConfig {

    @Value("${shiro.redis.host}")
    private String host;

    @Value("${shiro.redis.timeout}")
    private int timeout;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }
}

package com.example.demo.conf;

import com.example.demo.auth.PermissionRealm;
import com.example.demo.common.entity.User;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
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.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.LinkedHashMap;

@Configuration
public class ShiroConfig {

    @Bean
    public RedisConfig redisConfig(){
        return new RedisConfig();
    }

    @Bean
    public RedisManager redisManager(){
        RedisManager redisManager = new RedisManager();     // crazycake 实现
        redisManager.setHost(redisConfig().getHost());
        redisManager.setTimeout(redisConfig().getTimeout());
        return redisManager;
    }

    @Bean
    public JavaUuidSessionIdGenerator sessionIdGenerator(){
        return new JavaUuidSessionIdGenerator();
    }

    @Bean
    public RedisSessionDAO sessionDAO(){
        RedisSessionDAO sessionDAO = new RedisSessionDAO(); // crazycake 实现
        sessionDAO.setRedisManager(redisManager());
        sessionDAO.setSessionIdGenerator(sessionIdGenerator()); //  Session ID 生成器
        return sessionDAO;
    }

    @Bean
    public SimpleCookie cookie(){
        SimpleCookie cookie = new SimpleCookie("SHAREJSESSIONID"); //  cookie的name,对应的默认是 JSESSIONID
        cookie.setHttpOnly(true);
        cookie.setPath("/");        //  path为 / 用于多个系统共享JSESSIONID
        return cookie;
    }

    @Bean
    public DefaultWebSessionManager sessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setGlobalSessionTimeout(redisConfig().getTimeout());    // 设置session超时
        sessionManager.setDeleteInvalidSessions(true);      // 删除无效session
        sessionManager.setSessionIdCookie(cookie());            // 设置JSESSIONID
        sessionManager.setSessionDAO(sessionDAO());         // 设置sessionDAO
        return sessionManager;
    }

    /**
     * 1\. 配置SecurityManager
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm());  // 设置realm
        securityManager.setSessionManager(sessionManager());    // 设置sessionManager
//        securityManager.setCacheManager(redisCacheManager()); // 配置缓存的话,退出登录的时候crazycake会报错,要求放在session里面的实体类必须有个id标识
        return securityManager;
    }

    /**
     * 2\. 配置缓存
     * @return
     */
//    @Bean
//    public CacheManager cacheManager(){
//        EhCacheManager ehCacheManager = new EhCacheManager();
//        ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
//        return ehCacheManager;
//    }

    @Bean
    public RedisCacheManager redisCacheManager(){
        RedisCacheManager cacheManager = new RedisCacheManager();   // crazycake 实现
        cacheManager.setRedisManager(redisManager());
        return cacheManager;
    }

    /**
     * 3\. 配置Realm
     * @return
     */
    @Bean
    public AuthorizingRealm realm(){
        PermissionRealm realm = new PermissionRealm();
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        // 指定加密算法
        matcher.setHashAlgorithmName("MD5");
        // 指定加密次数
        matcher.setHashIterations(10);
        // 指定这个就不会报错
        matcher.setStoredCredentialsHexEncoded(true);
        realm.setCredentialsMatcher(matcher);
        return realm;
    }

    /**
     * 4\. 配置LifecycleBeanPostProcessor,可以来自动的调用配置在Spring IOC容器中 Shiro Bean 的生命周期方法
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 5\. 启用IOC容器中使用Shiro的注解,但是必须配置第四步才可以使用
     * @return
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        return new DefaultAdvisorAutoProxyCreator();
    }

    /**
     * 6\. 配置ShiroFilter
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(){
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        // 静态资源
        map.put("/css/**", "anon");
        map.put("/js/**", "anon");

        // 公共路径
        map.put("/login", "anon");
        map.put("/register", "anon");
        //map.put("/*", "anon");

        // 登出,项目中没有/logout路径,因为shiro是过滤器,而SpringMVC是Servlet,Shiro会先执行
        map.put("/logout", "logout");

        // 授权
        map.put("/user/**", "authc,roles[user]");
        map.put("/admin/**", "authc,roles[admin]");

        // everything else requires authentication:
        map.put("/**", "authc");

        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 配置SecurityManager
        factoryBean.setSecurityManager(securityManager());
        // 配置权限路径
        factoryBean.setFilterChainDefinitionMap(map);
        // 配置登录url
        factoryBean.setLoginUrl("/");
        // 配置无权限路径
        factoryBean.setUnauthorizedUrl("/unauthorized");
        return factoryBean;
    }

    /**
     * 配置RedisTemplate,充当数据库服务
     * @return
     */
    @Bean
    public RedisTemplate<String,User> redisTemplate(RedisConnectionFactory connectionFactory){
        RedisTemplate<String,User> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<User>(User.class));
        return redisTemplate;
    }

}

package com.example.demo.service;
import com.example.demo.common.entity.User;
import java.util.List;
public interface UserService {

    void addUser(User user);

    User login(User user);

    List<User> getUsers();

}

package com.example.demo.service.impl;

import com.example.demo.common.PasswordUtils;
import com.example.demo.common.entity.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private RedisTemplate<String, User> redisTemplate;

    @Override
    public void addUser(User user) {
        user.setPassword(PasswordUtils.saltAndMd5(user.getUsername(),user.getPassword()));  // 加密
        redisTemplate.boundHashOps("users").put(user.getUsername(), user);
    }

    @Override
    public User login(User user) {
        user.setPassword(PasswordUtils.saltAndMd5(user.getUsername(),user.getPassword()));  // 加密
        User u = (User) redisTemplate.boundHashOps("users").get(user.getUsername());
        if (u == null || !check(user, u)){
            return null;
        }
        return u;
    }

    @Override
    public List<User> getUsers() {
        List<Object> list = redisTemplate.boundHashOps("users").values();
        List<User> users = new ArrayList<>();
        list.forEach(u->{
            users.add((User) u);
        });
        return users;
    }

    private boolean check(User a, User b){
        if (a.getUsername().equals(b.getUsername()) && a.getPassword().equals(b.getPassword())){
            return true;
        }
        return false;
    }
}

package com.example.demo.controller;

import com.example.demo.common.entity.User;
import com.example.demo.common.response.BaseResponse;
import com.example.demo.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

@RestController
public class IndexController {

    @Autowired
    private UserService userService;

    @RequestMapping("/")
    public ModelAndView index(){
        return new ModelAndView("index");
    }

    @RequestMapping("/login")
    public BaseResponse<String> login(@RequestBody User user){
        BaseResponse<String> response = new BaseResponse<>(0,"登陆成功");
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(
                user.getUsername(), user.getPassword());
        subject.login(token);
        response.setData("/home");
        return response;
    }

    @RequestMapping("/register")
    public BaseResponse register(@RequestBody User user){
        userService.addUser(user);
        return new BaseResponse(0,"注册成功");
    }

    @RequestMapping("/home")
    public ModelAndView home(){
        ModelAndView mv = new ModelAndView("home");
        mv.addObject("users", userService.getUsers());
        return mv;
    }
}

server.port=8080 

spring.redis.host=127.0.0.1
spring.redis.port=6379

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Index</title>
    <link th:href="@{css/index.css}" rel="stylesheet" type="text/css">
</head>
<body>
    <div class="container"> 
        <div class="main">
            <div class="left">
                <div class="form-group">
                    <input type="text" name="username" placeholder="请输入用户名">
                </div>
                <div class="form-group">
                    <input type="password" name="password" placeholder="请输入密码">
                </div>
                <div class="form-group">
                    <a href="javascript:;" id="login">登录</a>
                </div>
                <div class="form-group">
                    <a href="/home">点我!不登录进不去</a>
                </div>
            </div>
            <div class="right">
                <div class="form-group">
                    <input type="text" name="username" placeholder="请输入用户名">
                </div>
                <div class="form-group">
                    <input type="password" name="password" placeholder="请输入密码">
                </div>
                <div class="form-group">
                    <input type="text" name="show" placeholder="自我介绍">
                </div>
                <div class="form-group">
                    <a href="javascript:;" id="register">注册</a>
                </div>
            </div>
        </div>
    </div> 
<script th:src="@{js/jquery-3.3.1.min.js}"></script>
<script th:src="@{js/index.js}"></script>
</body>
</html>

二、本地测试

通过nginx 启动两个不同端口的jar(8081、8082)

upstream myapp{
    server 127.0.0.1:8081 weight=1;
    server 127.0.0.1:8082 weight=1;
}

server{
        listen       80;
        server_name  myapp;

        location / {
            proxy_pass http://myapp;
            proxy_set_header   Host             $host;
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
        }
}

项目启动会,访问登录 就会发现cookie里存在SHAREJSESSIONID了 redis里也有对应的SessionId了。

三、生产环境&ShiroConfig配置

很多时候生产环境的redis都是集群化,这里的配置就有一点不同。

下面贴一下我公司项目的ShiroConfig配置,跟上面的demo不是一回事 大体思想还是一致的。

package cn.pconline.pcloud.admin.config;

import cn.pconline.pcloud.admin.service.RoleService;
import cn.pconline.pcloud.admin.service.UserService;
import cn.pconline.pcloud.base.entity.system.Resource;
import cn.pconline.pcloud.base.entity.system.Role;
import cn.pconline.pcloud.base.entity.system.User;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisClusterManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
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 org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import java.util.*;

/**
 * @Description Shiro配置 支持session集群
 * @Author jie.zhao
 * @Date 2020/3/31 13:54
 */
@Configuration
public class ShiroConfig {

    private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

    @Value("${app.domain:/}")
    private String domain;

    @Value("${spring.redis.cluster.nodes}")
    private String redisClusterNodes;

    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    private static final int redis_expire = 1000 * 60 * 60 * 2;

    private static final long session_expire = 1000 * 60 * 60 * 2;

    /**
     * 授权凭证(启动项目时加载)
     * 对应 realm.doGetAuthorizationInfo()
     *
     * @param securityManager
     * @return
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        shiroFilter.setLoginUrl(domain + "admin/login");
        shiroFilter.setSuccessUrl(domain + "admin/index");
        // 没权限时跳转至该页面
        shiroFilter.setUnauthorizedUrl(domain + "admin/permission/died");
        // anon、authc、user对应realm.doGetAuthenticationInfo(..)登录认证
        // perms、roles、ssl、est、port对应realm.doGetAuthorizationInfo(..)授权认证
        // 设置过滤器链接集合 注意:Map要支持顺序,授权配置后出
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilter;
    }

    @Bean
    public RedisClusterManager redisClusterManager() {
        RedisClusterManager redisManager = new RedisClusterManager();
        redisManager.setHost(redisClusterNodes);
        return redisManager;
    }

    @Bean
    public RedisSessionDAO redisSessionDAO(RedisClusterManager redisClusterManager) {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisClusterManager);
        //  Session ID 生成器
        redisSessionDAO.setSessionIdGenerator(new JavaUuidSessionIdGenerator());
        return redisSessionDAO;
    }

    @Bean
    public SimpleCookie cookie() {
        //  cookie的name,对应的默认是 JSESSIONID
        SimpleCookie cookie = new SimpleCookie("SHAREJSESSIONID");
        cookie.setHttpOnly(true);
        //  path为 / 用于多个系统共享JSESSIONID
        cookie.setPath("/");
        return cookie;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisClusterManager redisClusterManager) {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisClusterManager);
        redisCacheManager.setExpire(redis_expire);
        redisCacheManager.setPrincipalIdFieldName("userId");
        return redisCacheManager;
    }

    @Bean
    public SecurityManager securityManager(AuthorizingRealm myShiroRealm, RedisSessionDAO redisSessionDAO, RedisCacheManager redisCacheManager) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        // 设置session超时
        sessionManager.setGlobalSessionTimeout(session_expire);
        // 删除无效session
        sessionManager.setDeleteInvalidSessions(true);
        // 设置JSESSIONID
        sessionManager.setSessionIdCookie(cookie());
        // 设置sessionDAO
        sessionManager.setSessionDAO(redisSessionDAO);

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setSessionManager(sessionManager);
        securityManager.setCacheManager(redisCacheManager);
        securityManager.setRememberMeManager(cookieRememberMeManager());
        securityManager.setRealm(myShiroRealm);
        return securityManager;
    }

    @Bean
    public AuthorizingRealm myShiroRealm() {
        AuthorizingRealm myShiroRealm = new AuthorizingRealm() {

            @Override
            protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
                logger.info("认证 --> MyShiroRealm.doGetAuthenticationInfo()");
                String username = (String) token.getPrincipal();
                User user = userService.findByAccount(username);
                String password = new String((char[]) token.getCredentials());
                // 账号不存在
                if (user == null) {
                    throw new UnknownAccountException("账号不存在!");
                }
                // 密码错误
                /*if (!MD5Utils.md5(password).equals(user.getPassword())) {
                    throw new IncorrectCredentialsException("账号或密码不正确");
                }*/
                // 账号锁定
                if (user.getIsLock() == 1) {
                    throw new LockedAccountException("账号已被锁定,请联系管理员!");
                }
                SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
                return info;
            }

            @Override
            protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
                logger.info("权限配置 --> MyShiroRealm.doGetAuthorizationInfo()");
                User user = (User) principals.getPrimaryPrincipal();
                SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
                Set<String> shiroPermissions = new HashSet<>();
                Set<String> roleSet = new HashSet<String>();
                // 加载你的角色
                List<Role> roles = roleService.list4Login(user);
                if (roles != null) {
                    for (Role role : roles) {
                        // 添加角色
                        roleSet.add(role.getRoleKey());
                        // 添加角色关联的资源
                        if (role.getRelResourceList() != null) {
                            for (Resource resource : role.getRelResourceList()) {
                                shiroPermissions.add(resource.getSourceKey());
                            }
                        }
                    }
                }
                authorizationInfo.setRoles(roleSet);
                authorizationInfo.setStringPermissions(shiroPermissions);
                return authorizationInfo;
            }
        };
        myShiroRealm.setCachingEnabled(true);
        myShiroRealm.setAuthorizationCachingEnabled(true);
        return myShiroRealm;
    }

    /**
     * DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理
     *
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
        proxyCreator.setProxyTargetClass(true);
        return proxyCreator;
    }

    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    public SimpleMappingExceptionResolver resolver() {
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "error/403");
        exceptionResolver.setExceptionMappings(properties);
        return exceptionResolver;
    }

    @Bean
    public SimpleCookie rememberMeCookie() {
        logger.info("ShiroConfiguration.rememberMeCookie()");
        //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");

        //<!-- 记住我cookie生效时间 ,单位秒;-->
        simpleCookie.setMaxAge(1800);
        return simpleCookie;
    }

    @Bean
    public CookieRememberMeManager cookieRememberMeManager() {
        logger.info("ShiroConfiguration.rememberMeManager()");
        CookieRememberMeManager manager = new CookieRememberMeManager();
        manager.setCookie(rememberMeCookie());
        return manager;
    }
}

参考:
https://my.oschina.net/u/4351575/blog/4134146

上一篇下一篇

猜你喜欢

热点阅读