废话不多说,直接上代码。示例如下:
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("");
}
</script>
</head>
<body style="background-color: cyan;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配置生效!
.