(轉)Spring Security 之Session管理配置


出處:https://www.cnblogs.com/jonban/p/session.html

廢話不多說,直接上代碼。示例如下:

 

1.   新建Maven項目  session

 

2.   pom.xml

復制代碼
<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.java</groupId>
    <artifactId>session</artifactId>
    <version>1.0.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
    </parent>


    <dependencies>

        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>


        <!-- 熱部署 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>springloaded</artifactId>
            <version>1.2.8.RELEASE</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>provided</scope>
        </dependency>

    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
復制代碼

 

3.   ResponseUtils.java

復制代碼
package javax.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletResponse;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * HTTP 輸出響應內容工具類
 * 
 * @author Logan
 * @createDate 2019-02-13
 * @version 1.0.0
 *
 */
public class ResponseUtils {

    /**
     * 發送HTTP響應信息
     * 
     * @param response HTTP響應對象
     * @param message 信息內容
     * @throws IOException 拋出異常,由調用者捕獲處理
     */
    public static void write(HttpServletResponse response, String message) throws IOException {
        response.setContentType("text/html;charset=UTF-8");

        try (
                PrintWriter writer = response.getWriter();
        ) {
            writer.write(message);
            writer.flush();
        }
    }

    /**
     * 發送HTTP響應信息,JSON格式
     * 
     * @param response HTTP響應對象
     * @param message 輸出對象
     * @throws IOException 拋出異常,由調用者捕獲處理
     */
    public static void write(HttpServletResponse response, Object message) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        ObjectMapper mapper = new ObjectMapper();

        try (
                PrintWriter writer = response.getWriter();
        ) {
            writer.write(mapper.writeValueAsString(message));
            writer.flush();
        }
    }

    /**
     * 下載文件
     * 
     * @param response HTTP響應對象
     * @param message 輸出對象
     * @throws IOException 拋出異常,由調用者捕獲處理
     */
    public static void write(HttpServletResponse response, File file) throws IOException {
        String fileName = file.getName();
        try (
                OutputStream out = response.getOutputStream();
                FileInputStream in = new FileInputStream(file);
        ) {

            // 對文件名進行URL轉義,防止中文亂碼
            fileName = URLEncoder.encode(fileName, "UTF-8");

            // 空格用URLEncoder.encode轉義后會變成"+",所以要替換成"%20",瀏覽器會解碼回空格
            fileName = fileName.replace("+", "%20");

            // "+"用URLEncoder.encode轉義后會變成"%2B",所以要替換成"+",瀏覽器不對"+"進行解碼
            fileName = fileName.replace("%2B", "+");
            response.setContentType("application/x-msdownload;charset=UTF-8");
            response.setHeader("Content-Disposition", "attachment; filename=" + fileName);

            byte[] bytes = new byte[4096];
            int len = -1;
            while ((len = in.read(bytes)) != -1) {
                out.write(bytes, 0, len);
            }
            out.flush();
        }
    }

}
復制代碼

 

4.   SessionStarter.java

復制代碼
package com.java;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * <blockquote><pre>
 * 
 * 主啟動類
 * 
 * </pre></blockquote>
 * 
 * @author Logan
 *
 */
@SpringBootApplication
public class SessionStarter {

    public static void main(String[] args) {
        SpringApplication.run(SessionStarter.class, args);
    }

}
復制代碼

 

5.   SessionInformationExpiredStrategyImpl.java

復制代碼
package com.java.session;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.utils.ResponseUtils;

import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;

/**
 * Session過期處理策略
 * 
 * @author Logan
 * @createDate 2019-02-13
 * @version 1.0.0
 *
 */
public class SessionInformationExpiredStrategyImpl implements SessionInformationExpiredStrategy {

    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {

        ResponseUtils.write(event.getResponse(), "你的賬號在另一地點被登錄");
    }

}
復制代碼

 

6.   ApplicationContextConfig.java

復制代碼
package com.java.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * 配置文件類
 * 
 * @author Logan
 * @createDate 2019-02-13
 * @version 1.0.0
 *
 */
@Configuration
public class ApplicationContextConfig {

    /**
     * 配置密碼編碼器,Spring Security 5.X必須配置,否則登錄時報空指針異常
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
復制代碼

 

7.   LoginConfig.java

復制代碼
package com.java.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import com.java.session.SessionInformationExpiredStrategyImpl;

/**
 * 登錄相關配置
 * 
 * @author Logan
 * @createDate 2019-02-13
 * @version 1.0.0
 *
 */
@Configuration
public class LoginConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()

                // 設置不需要授權的請求
                .antMatchers("/js/*", "/login.html").permitAll()

                // 其它任何請求都需要驗證權限
                .anyRequest().authenticated()

                // 設置自定義表單登錄頁面
                .and().formLogin().loginPage("/login.html")

                // 設置登錄驗證請求地址為自定義登錄頁配置action ("/login/form")
                .loginProcessingUrl("/login/form")

                // 設置默認登錄成功跳轉頁面
                .defaultSuccessUrl("/main.html")

                /* session 管理 */
                .and().sessionManagement()

                // 設置Session失效跳轉頁面
                .invalidSessionUrl("/login.html")

                // 設置最大Session數為1
                .maximumSessions(1)

                // 設置Session過期處理策略
                .expiredSessionStrategy(new SessionInformationExpiredStrategyImpl()).and()

                // 暫時停用csrf,否則會影響驗證
                .and().csrf().disable();
    }

}
復制代碼

 

8.   SecurityUserDetailsService.java

復制代碼
package com.java.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * UserDetailsService實現類
 * 
 * @author Logan
 * @createDate 2019-02-13
 * @version 1.0.0
 *
 */
@Component
public class SecurityUserDetailsService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // 數據庫存儲密碼為加密后的密文(明文為123456)
        String password = passwordEncoder.encode("123456");

        System.out.println("username: " + username);
        System.out.println("password: " + password);

        // 模擬查詢數據庫,獲取屬於Admin和Normal角色的用戶
        User user = new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("Admin,Normal"));

        return user;
    }

}
復制代碼

 

9.   靜態資源文件如下

static/login.html

static/main.html

 

 

 

10.  login.html

復制代碼
<!DOCTYPE html>
<html>

    <head>
        <title>登錄</title>
        <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
    </head>

    <body>

        <!--登錄框-->
        <div align="center">
            <h2>用戶自定義登錄頁面</h2>
            <fieldset style="width: 300px;">
                <legend>登錄框</legend>
                <form action="/login/form" method="post">
                    <table>
                        <tr>
                            <th>用戶名:</th>
                            <td><input name="username" value="Logan" /> </td>
                        </tr>
                        <tr>
                            <th>密碼:</th>
                            <td><input type="password" name="password" value="123456" /> </td>
                        </tr>
                        <tr>
                            <th></th>
                            <td></td>
                        </tr>
                        <tr>
                            <td colspan="2" align="center"><button type="submit">登錄</button></td>
                        </tr>
                    </table>
                </form>
            </fieldset>

        </div>

    </body>

</html>
復制代碼

 

 

11.   main.html

復制代碼
<html>

    <head>
        <title>首頁</title>
        <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
        <script>
            window.onclick = function() {
                window.open("http://www.cnblogs.com/jonban/");
            }
        </script>
    </head>

    <body style="text-align: center;">
        <h1><span style="text-align:center;color:purple;cursor: pointer;">Designed by Logan.</span></h1>
        <canvas id="c"></canvas>
        <script>
            var b = document.body;
            var c = document.getElementsByTagName('canvas')[0];
            var a = c.getContext('2d');
            document.body.clientWidth;
        </script>

        <script>
            with(m = Math)
            C = cos, S = sin, P = pow, R = random;
            c.width = c.height = f = 613;
            h = -250;

            function p(a, b, c) {
                if(c > 60)
                    return [S(a * 7) * (13 + 5 / (.2 + P(b * 4, 4))) - S(b) * 50,
                        b * f + 50,
                        625 + C(a * 7) * (13 + 5 / (.2 + P(b * 4, 4))) + b * 400,
                        a * 1 - b / 2, a
                    ];
                A = a * 2 - 1;
                B = b * 2 - 1;
                if(A * A + B * B < 1) {
                    if(c > 37) {
                        n = (j = c & 1) ? 6 : 4;
                        o = .5 / (a + .01) + C(b * 125) * 3 - a * 300;
                        w = b * h;
                        return [o * C(n) + w * S(n) + j * 610 - 390, o * S(n) - w * C(n) + 550 - j * 350, 1180 + C(B + A) * 99 - j * 300, .4 - a * .1 + P(1 - B * B, -h * 6) * .15 - a * b * .4 + C(a + b) / 5 + P(C((o * (a + 1) + (B > 0 ? w : -w)) / 25), 30) * .1 * (1 - B * B), o / 1e3 + .7 - o * w * 3e-6]
                    }
                    if(c > 32) {
                        c = c * 1.16 - .15;
                        o = a * 45 - 20;
                        w = b * b * h;
                        z = o * S(c) + w * C(c) + 620;
                        return [o * C(c) - w * S(c), 28 + C(B * .5) * 99 - b * b * b * 60 - z / 2 - h, z, (b * b * .3 + P((1 - (A * A)), 7) * .15 + .3) * b, b * .7]
                    }
                    o = A * (2 - b) * (80 - c * 2);
                    w = 99 - C(A) * 120 - C(b) * (-h - c * 4.9) + C(P(1 - b, 7)) * 50 + c * 2;
                    z = o * S(c) + w * C(c) + 700;
                    return [o * C(c) - w * S(c), B * 99 - C(P(b, 7)) * 50 - c / 3 - z / 1.35 + 450, z, (1 - b / 1.2) * .9 + a * .1, P((1 - b), 20) / 4 + .05]
                }
            }
            setInterval('for(i=0;i<1e4;i++)if(s=p(R(),R(),i%46/.74)){z=s[2];x=~~(s[0]*f/z-h);y=~~(s[1]*f/z-h);if(!m[q=y*f+x]|m[q]>z)m[q]=z,a.fillStyle="rgb("+~(s[3]*h)+","+~(s[4]*h)+","+~(s[3]*s[3]*-80)+")",a.fillRect(x,y,1,1)}', 0)
        </script>
    </body>

</html>
復制代碼

 

12.   運行SessionStarter.java ,啟動項目

 

瀏覽器輸入 http://localhost:8080/main.html

自動跳轉到登錄頁面

 

輸入如下信息:

 

User:Logan

Password:123456

 

 單擊【登錄】按鈕,自動跳轉到首頁。

 

換用其它瀏覽器再次訪問 http://localhost:8080/main.html

輸入相同用戶信息: 

User:Logan

Password:123456

 

單擊【登錄】按鈕,自動跳轉到首頁。

 

刷新第一個瀏覽器,提示

你的賬號在另一地點被登錄

 

 再次刷新,跳轉到登錄頁面。

 

Session配置生效!

 

.


免責聲明!

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



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