SpringBoot+Shiro+Redis共享Session入門小栗子


在單機版的Springboot+Shiro的基礎上,這次實現共享Session。

這里沒有自己寫RedisManager、SessionDAO。用的 crazycake 寫的開源插件

pom.xml

<?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.1.0</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>
View Code

 redis配置文件

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;
    }
}

 

Shiro配置文件

 

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;
    }

}

 

UserService

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();

}

 

impl

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;
    }
}

 

controller

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 SimpleController {

    @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;
    }
}

 redis.properties

shiro.redis.host=localhost:6379
shiro.redis.timeout=1800000

 

applicatin.properties

#server.port=8080
server.port=8081
#server.port=8082

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

 

index.html

<!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="header">
            <h2>初級SpringBoot+Shiro小栗子 Node-One</h2>
            <!--<h2>初級SpringBoot+Shiro小栗子 Node-Two</h2>-->
        </div>
        <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>
    <!--<div class="tip-wrap">-->
        <!--<div class="tip">似懂非懂</div>-->
    <!--</div>-->
<script th:src="@{js/jquery-3.3.1.min.js}"></script>
<script th:src="@{js/index.js}"></script>
</body>
</html>

 

home.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
    <link th:href="@{css/index.css}" rel="stylesheet" type="text/css">
</head>
<body>
<div class="container">
    <div class="header">
        <h2>初級SpringBoot+Shiro小栗子 Node-One</h2>
        <!--<h2>初級SpringBoot+Shiro小栗子 Node-Two</h2>-->
        <a href="/logout">退出登錄</a>
    </div>
    <div class="main">
        <table class="table">
            <thead>
            <tr>
                <th>Username</th>
                <th>Password</th>
                <th>Show</th>
            </tr>
            </thead>
            <tbody>
            <tr th:each="u : ${users}">
                <td>[[${u.username}]]</td>
                <td>[[${u.password}]]</td>
                <td>[[${u.show}]]</td>
            </tr>
            </tbody>
        </table>
    </div>
</div>
</body>
</html>

 

以上兩種配置各打包一次(記得留着打包好的jar包)

下載Nginx

 

解壓到無中文目錄,修改Nginx配置文件

    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;
            }
    }

 

 

 到此,先啟動兩個jar包(分別是8081,Node-One;8082,Node-Two)

然后啟動Nginx

瀏覽器訪問:http://localhost/

 

刷新看看..

..

隨便在一個節點上注冊,登錄,然后刷新到另外一個節點,發現不用登錄就可以訪問權限資源

 

..

 

GitHub


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM