前言
本文件所记录的是使用SpringSecurity实现remember me功能,有兴趣的朋友可以继续阅读,有何不足之处还请各位指出(本文未对用户 - 角色 - 权限三者的关系进行详细介绍详情见SpringBoot集成SpringSecurity(一) 初识SpringSecurity;
SpringSecurity认证流程:
SpringSecurity的认证流程主要是通过一系列的Filter对请求进行拦截处理
SpringSecurity核心功能:
- 认证(你是谁)
- 授权(你能干什么)
- 攻击防护(防止伪造身份)
简单的介绍一下环境:
gradle构建的SpringBoot项目,数据库使用的是MongoDB(无他,用的顺手,顺带希望能丰富一下SpringSecurity文章圈)依赖如下:
plugins {
id 'org.springframework.boot' version '2.2.5.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'cn.gotham'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.0.4.RELEASE'
implementation 'org.apache.commons:commons-lang3:3.8.1'
implementation 'commons-codec:commons-codec:1.11'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'org.springframework.security:spring-security-test'
}
test {
useJUnitPlatform()
}
首先咱们先实现简单的登录认证
先准备几个简陋的前端页面:
login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title>登录界面(初识SpringSecurity)</title>
</head>
<body>
<div id="login-form">
<div>
<label>账户:</label>
<input type="text" name="username" id="username" />
</div>
<div>
<label>密码:</label>
<input type="password" name="password" id="password" />
</div>
<div>
<input name="wam_remember_me" title="记住我" type="checkbox" value="true" >
<span>记住我</span>
</div>
<div style="display: inline;">
<input type="text" name="vercode" id="vercode" placeholder="图形验证码" style="width: 6.25rem;height:2.125rem;">
</div>
<div style="display: inline; margin-top: 0.625rem;">
<img th:src="@{/public/base/img/code.jpg}" style="width: 6.25rem;height:2.125rem;">
</div>
<div>
<button id="submit" >登 录</button>
</div>
</div>
<script type="application/javascript" th:src="@{/public/base/js/jquery-3.2.1.min.js}"></script>
</body>
</html>
index.html
<!DOCTYPE html>
<html >
<head>
<meta charset="utf-8">
<title>登录成功访问的第一个界面</title>
</head>
<body>
<h1>登录成功访问的第一个界面</h1>
<ul>
<li>
<a th:href="@{/admin}" >ADMIN角色可访问</a>
</li>
<li>
<a th:href="@{/user}" >USER角色可访问</a>
</li>
<li>
<a th:href="@{/common}" >COMMON角色可访问</a>
</li>
</ul>
</script>
</body>
</html>
user.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>USER页面</h1>
</body>
</html>
admin.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>ADMIN页面</h1>
</body>
</html>
common.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>ADMIN USER皆可访问</h1>
</body>
</html>
贴一下model
实体类主要包括
- User.java :用户实体
- Role.java :角色实体类
- Authority.java :权限枚举类
User.java
package cn.gotham.spring_security_02.user.model;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.List;
/**
* 用户模型
* @author tanchong
* Create Date: 2020/3/8
*/
@Document("user")
public class User {
@Id
private ObjectId id;
private String username;
private String password;
private String email;
private List<Role> roles;
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
", roles=" + roles +
'}';
}
}
Role.java
package cn.gotham.spring_security_02.user.model;
import cn.gotham.spring_security_02.user.enumeration.Authority;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import java.util.List;
/**
* 角色模型
* @author tanchong
* Create Date: 2020/3/8
*/
@Document("role")
public class Role {
@Id
private ObjectId id;
@Field("role_name")
private String roleName;
private List<Authority> authorityList;
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public List<Authority> getAuthorityList() {
return authorityList;
}
public void setAuthorityList(List<Authority> authorityList) {
this.authorityList = authorityList;
}
}
Authority.java
package cn.gotham.spring_security_02.user.enumeration;
/**
*
* 权限列表
* @author tanchong
* Create Date: 2020/3/8
*/
public enum Authority {
WAM_USER("GOTHAM-用户"),
WAM_ADMIN("GOTHAM-管理员");
private String description;
Authority(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
还差两个Repository
RoleRespository.java
package cn.gotham.spring_security_02.user.repository;
import cn.gotham.spring_security_02.user.model.Role;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
/**
*
* @author tanchong
* Create Date: 2020/3/8
*/
public interface RoleRepository extends MongoRepository<Role, ObjectId> {
}
UserRespository.java
package cn.gotham.spring_security_02.user.repository;
import cn.gotham.spring_security_02.user.model.User;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.Optional;
/**
* @author tanchong
* Create Date: 2020/3/8
*/
public interface UserRepository extends MongoRepository<User, ObjectId> {
Optional<User> findByUsername(String username);
}
最好写一下Controller
BaseController.java
package cn.gotham.spring_security_02.base.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
*
* @author tanchong
* Create Date: 2020/3/15
*/
@Controller
public class BaseController {
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
/**
* 登录成功跳转页面
* @return
*/
@GetMapping("/")
public String index(){
return "web/index";
}
/**
* 跳转登录界面
* @return
*/
@GetMapping("/login")
public String login(){
LOGGER.info("登录页面");
return "web/login";
}
}
SpringSecurityController.java
package cn.gotham.spring_security_02.user.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* 准备了三个页面 用来测试SpringSecurity的权限控制
* @author tanchong
* Create Date: 2020/3/15
*/
@Controller
public class SpringSecurityController {
@GetMapping("/admin")
public String admin() {
return "web/verify/admin";
}
@GetMapping("/user")
public String user() {
return "web/verify/user";
}
@GetMapping("/common")
public String common() {
return "web/verify/common";
}
}
初始化数据
初始化数据: * 角色初始化: * ADMIN角色 * 权限:WAM_ADMIN * USER角色 * 权限:WAM_USER * COMMON角色 * 权限:WAM_ADMIN、WAM_USER * 用户初始化: * ADMIN用户 * 角色: ADMIN角色 * USER用户 * 角色:USER角色 * COMMON用户 * 角色:COMMON角色
package cn.gotham.spring_security_02;
import cn.gotham.spring_security_02.user.enumeration.Authority;
import cn.gotham.spring_security_02.user.model.Role;
import cn.gotham.spring_security_02.user.model.User;
import cn.gotham.spring_security_02.user.repository.RoleRepository;
import cn.gotham.spring_security_02.user.repository.UserRepository;
import org.apache.commons.codec.digest.DigestUtils;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@SpringBootTest
class SpringSecurity02ApplicationTests {
@Autowired
private RoleRepository roleRepository;
@Autowired
private UserRepository userRepository;
@Test
void contextLoads() {
// 创建ADMIN 角色
var adminRole = new Role();
adminRole.setRoleName("ADMIN");
var adminAuthorities = Arrays.stream(Authority.values())
.filter(Objects::nonNull)
.filter(authority -> authority.getDescription().equals("GOTHAM-管理员"))
.collect(Collectors.toList());
adminRole.setAuthorityList(adminAuthorities);
roleRepository.insert(adminRole);
// 创建USER 角色
var userRole = new Role();
userRole.setRoleName("USER");
var userAuthorities = Arrays.stream(Authority.values())
.filter(Objects::nonNull)
.filter(authority -> authority.getDescription().equals("GOTHAM-用户"))
.collect(Collectors.toList());
userRole.setAuthorityList(userAuthorities);
roleRepository.insert(userRole);
// 创建所有权限角色(ADMIN+USER)
var commonRole = new Role();
commonRole.setRoleName("ADMIN+USER");
var commonAuthorities = Arrays.stream(Authority.values())
.filter(Objects::nonNull)
.collect(Collectors.toList());
commonRole.setAuthorityList(commonAuthorities);
roleRepository.insert(commonRole);
// 再创建三个用户 分别为user admin common
var user = new User();
user.setUsername("user");
user.setPassword(DigestUtils.md5Hex("123user"));
user.setEmail("1097172038@qq.com");
user.setRoles(List.of(userRole));
userRepository.insert(user);
var admin = new User();
admin.setUsername("admin");
admin.setPassword(DigestUtils.md5Hex("123admin"));
admin.setEmail("1097172038@qq.com");
admin.setRoles(List.of(adminRole));
userRepository.insert(admin);
var common = new User();
common.setUsername("common");
common.setPassword(DigestUtils.md5Hex("123common"));
common.setEmail("1097172038@qq.com");
common.setRoles(List.of(commonRole));
userRepository.insert(common);
}
}
配置SpringSecurity
UserSecurityConfig.class
package cn.gotham.spring_security_02.common.config;
import cn.gotham.spring_security_02.user.enumeration.Authority;
import cn.gotham.spring_security_02.user.model.Role;
import cn.gotham.spring_security_02.user.repository.UserRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Objects;
/**
* 该类是 Spring Security 的配置类,该类的三个注解分别是标识该类是配置类、开启全局 Securtiy 注解。
* 这里我们还指定了密码的加密方式(5.0 版本强制要求设置),因为我们数据库是明文存储的,所以明文返回即可,如下所示:
* @author tanchong
* Create Date: 2020/3/8
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class UserSecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(UserSecurityConfig.class);
private UserRepository userRepository;
private ObjectMapper objectMapper;
@Autowired
public UserSecurityConfig(UserRepository userRepository, ObjectMapper objectMapper) {
this.userRepository = userRepository;
this.objectMapper = objectMapper;
}
/**
*
* 可将该方法单独封装、本文采用重写方法
* 重写 userDetailsService() 将用户信息和权限注入进来
*/
@Override
protected UserDetailsService userDetailsService() {
return (username) -> {
// 从数据库中取出用户信息
var user = userRepository.findByUsername(username).orElse(null);
// 判断用户是否存在
if (user == null) {
throw new UsernameNotFoundException("用户[ "+username+" ]不存在!");
}
// 返回UserDetails实现类写法一
// var authorities = new ArrayList<GrantedAuthority>();
// List<Role> roles = user.getRoles();
// roles.forEach(role -> {
// role.getAuthorityList().forEach(authority -> {
// authorities.add(new SimpleGrantedAuthority(authority.toString()));
// });
// });
// UserDetails build = User.withUsername(username).password(user.getPassword()).authorities(authorities).build();
// 返回UserDetails实现类写法二
return User.withUsername(username) //添加用户名
.password(user.getPassword()) //添加用户密码
//添加用户权限
.authorities(user.getRoles()
.stream()
.filter(Objects::nonNull)
.map(Role::getAuthorityList)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.filter(Objects::nonNull)
.map(Authority::toString)
.map(SimpleGrantedAuthority::new)
.toArray(SimpleGrantedAuthority[]::new))
.build();
};
}
/**
* 指定密码 加密 与 校验
* (加密方式可修改)
* @return
*/
@Bean
public PasswordEncoder md5PasswordEncoderForUser(){
return new PasswordEncoder(){
@Override
public String encode(CharSequence rawPassword) {
return DigestUtils.md5Hex(rawPassword.toString());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(encode(rawPassword));
}
};
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
.frameOptions()
.disable();
http.authorizeRequests()
//设置拦截忽略,可以对以下资源放行
.antMatchers(
"/login", "/public/**")
.permitAll()
.anyRequest()
// 登录必须权限
.hasAnyAuthority("WAM_USER");
http.formLogin()
// 设置登录页
.loginPage("/login")
// 设置登录处理接口
.loginProcessingUrl("/api/v1/login")
.permitAll()
.defaultSuccessUrl("/")
// 设置登录成功处理方法
.successHandler(authenticationSuccessHandler());
http.csrf()
.disable();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 替换默认userDetailsService () 方法
auth.userDetailsService(userDetailsService());
}
private AuthenticationSuccessHandler authenticationSuccessHandler(){
return (HttpServletRequest request, HttpServletResponse response, Authentication authentication ) ->{
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("application/json");
var root = objectMapper.createObjectNode();
root.put("redirect",
request.getRequestURI().equals("/api/v1/login") ? "/" : request.getRequestURI());
response.getOutputStream().write(root.toString().getBytes());
};
}
}
编写登录逻辑
<script>
function login() {
var username = $("#username").val();
var password = $("#password").val();
$.ajax({
url : "/api/v1/login",
method : "POST",
dataType : "JSON",
data : {
username : username,
password : password
},
success: function (result) {
location.href = result['redirect'];
},
error: function (event) {
}
})
}
</script>
在login.html 登录按钮处调用
<div>
<button id="submit" onclick="login()">登 录</button>
</div>
运行程序:
尝试访问登录页面以外的接口,均被拦截跳转至登录页面
尝试使用admin账户登录
Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback. Sun Mar 15 17:03:20 CST 2020 There was an unexpected error (type=Forbidden, status=403). Forbidden
服务器返回错误信息 forbidden status=403
what?没有权限?
为啥没有权限呢,在SpringSecurity配置类UserSecurityConfig.java的
configure(HttpSecurity http)中,我们做了如下配置
http.authorizeRequests() //设置拦截忽略,可以对以下资源放行 .antMatchers( "/login", "/public/**") .permitAll() .anyRequest() // 登录必须权限 .hasAnyAuthority("WAM_USER"); // 这就是报错403的原因
我们的admin账户在初始化时,只具备了WAM_ADMIN 权限,故登录成功之后会保存403,修改
.hasAnyAuthority("WAM_USER","WAM_ADMIN")
这一次登录成功了
实现权限访问控制
对应角色访问对于页面, 我们需要在前端页面以及Controller层做些改造
先看前端页面
改造一下index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="utf-8">
<title>登录成功访问的第一个界面</title>
</head>
<body>
<h1>登录成功访问的第一个界面</h1>
<ul>
<li sec:authorize="hasAuthority('WAM_ADMIN')">
<a th:href="@{/admin}" >ADMIN角色可访问</a>
</li>
<li sec:authorize="hasAuthority('WAM_USER')">
<a th:href="@{/user}" >USER角色可访问</a>
</li>
<li sec:authorize="hasAnyAuthority('WAM_ADMIN','WAM_USER')">
<a th:href="@{/common}" >COMMON角色可访问</a>
</li>
</ul>
</body>
</html>
以及SpringSecurityController.java
package cn.gotham.spring_security_02.user.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* 准备了三个页面 用来测试SpringSecurity的权限控制
* @author tanchong
* Create Date: 2020/3/15
*/
@Controller
public class SpringSecurityController {
// 需要WAM_ADMIN 才能访问
@PreAuthorize("hasAuthority('WAM_ADMIN')")
@GetMapping("/admin")
public String admin() {
return "web/verify/admin";
}
// WAM_USER 才能访问
@PreAuthorize("hasAuthority('WAM_USER')")
@GetMapping("/user")
public String user() {
return "web/verify/user";
}
// WAM_ADMIN WAM_USER 任意一个权限即可访问
@PreAuthorize("hasAnyAuthority('WAM_ADMIN','WAM_USER')")
@GetMapping("/common")
public String common() {
return "web/verify/common";
}
}
运行访问:
登录admin账户
登录user账户
登录common账户
添加退出登录以及remember-me
实现退出登录:我们需要再配置一下SpringSecuroty,添加登出配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
.frameOptions()
.disable();
http.authorizeRequests()
//设置拦截忽略,可以对以下资源放行
.antMatchers(
"/login", "/public/**")
.permitAll()
.anyRequest()
// 登录必须权限
.hasAnyAuthority("WAM_USER","WAM_ADMIN");
http.formLogin()
// 设置登录页
.loginPage("/login")
// 设置登录处理接口
.loginProcessingUrl("/api/v1/login")
.permitAll()
.defaultSuccessUrl("/")
// 设置登录成功处理方法
.successHandler(authenticationSuccessHandler());
http.logout() //登出配置
.logoutUrl("/api/v1/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler())
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
http.csrf()
.disable();
}
在index.html中我们请求一下这个登出接口,即可实现登出效果
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="utf-8">
<title>登录成功访问的第一个界面</title>
</head>
<body>
<h1>登录成功访问的第一个界面</h1>
<button onclick="logout()">退出登录</button>
<ul>
<li sec:authorize="hasAuthority('WAM_ADMIN')">
<a th:href="@{/admin}" >ADMIN角色可访问</a>
</li>
<li sec:authorize="hasAuthority('WAM_USER')">
<a th:href="@{/user}" >USER角色可访问</a>
</li>
<li sec:authorize="hasAnyAuthority('WAM_ADMIN','WAM_USER')">
<a th:href="@{/common}" >COMMON角色可访问</a>
</li>
</ul>
</body>
<script type="application/javascript" th:src="@{/public/base/js/jquery-3.2.1.min.js}"></script>
<script>
function logout() {
$.ajax({
url: "/api/v1/logout",
method: 'POST',
success: function (result) {
location.href = result['redirect'];
}
});
}
</script>
</html>
Remember me实现流程
- 当用户首次发送 login request 时, 会先经过
UsernamePasswordAuthenticationFilter
进行校验,校验通过后会自动调用RememberMeService
将 Token 保存进数据库,同时将 Token 返回写入到浏览器 cookie 中- 在token未失效再次登录时,request中携带有token发送request时会直接读取到对应的token和username,然后根据username获取到用户的信息
具体咱们还是来看看mongodb的实现步骤
1.定义MongoTokenRepositoryImpl.class 实现PersistentTokenRepository接口重写其方法
package cn.gotham.spring_security_02.common.repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import java.util.Date;
/**
* @author tanchong
* Create Date: 2020/3/15
*/
public class MongoTokenRepositoryImpl implements PersistentTokenRepository {
private static final Logger LOGGER = LoggerFactory.getLogger(MongoTokenRepositoryImpl.class);
private static final String PERSISTENT_COLLETCTION = "persistent_logins";
@Autowired
private MongoTemplate mongoTemplate;
@Override
public void createNewToken(PersistentRememberMeToken token) {
removeUserTokens(token.getUsername());
mongoTemplate.insert(token,PERSISTENT_COLLETCTION);
LOGGER.info("创建用户 [ {} ] TOKEN",token.getUsername());
}
/**
* 更新用户TOKEN
* @param series
* @param tokenValue
* @param lastUsed
*/
@Override
public void updateToken(String series, String tokenValue, Date lastUsed) {
var query = new Query(Criteria.where("series").is(series));
Update update = new Update();
update.set("tokenValue",tokenValue);
update.set("date",lastUsed);
mongoTemplate.updateFirst(query,update,PERSISTENT_COLLETCTION);
LOGGER.info("更新用户TOKEN [{}]",series);
}
/**
* 获取用户TOKEN
* @param seriesId
* @return
*/
@Override
public PersistentRememberMeToken getTokenForSeries(String seriesId) {
var query = new Query(Criteria.where("series").is(seriesId));
var persistentRememberMeToken = mongoTemplate.findOne(query, PersistentRememberMeToken.class, PERSISTENT_COLLETCTION);
LOGGER.info("获取用户TOKEN [ {} ]",seriesId);
return persistentRememberMeToken;
}
/**
* 移除用户TOKEN
* @param username 用户名称
*/
@Override
public void removeUserTokens(String username) {
var query = new Query(Criteria.where("username").is(username));
mongoTemplate.remove(query,PersistentRememberMeToken.class,PERSISTENT_COLLETCTION);
LOGGER.info("移除用户 [ {} ] TOKEN",username);
}
}
2.在SpringSecurity配置类中添加
@Bean public PersistentTokenRepository persistentTokenRepository(){ return new MongoTokenRepositoryImpl(); }
3.配置
http.rememberMe() // 记住我 .rememberMeParameter("wam_remember_me") // 前端checkbox传递过来的参数name必须对应此值,为true时实现remember me .tokenRepository(persistentTokenRepository()) .userDetailsService(userDetailsService()) .tokenValiditySeconds(7 * 24 * 60 * 60);
4.前端
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"> <title>登录界面(初识SpringSecurity)</title> </head> <body> <div id="login-form"> <div> <label>账户:</label> <input type="text" name="username" id="username" /> </div> <div> <label>密码:</label> <input type="password" name="password" id="password" /> </div> <div> <input name="wam_remember_me" title="记住我" type="checkbox" value="true" > <span>记住我</span> </div> <div style="display: inline;"> <input type="text" name="vercode" id="vercode" placeholder="图形验证码" style="width: 6.25rem;height:2.125rem;"> </div> <div style="display: inline; margin-top: 0.625rem;"> <img th:src="@{/public/base/img/code.jpg}" style="width: 6.25rem;height:2.125rem;"> </div> <div> <button id="submit" onclick="login()">登 录</button> </div> </div> <script type="application/javascript" th:src="@{/public/base/js/jquery-3.2.1.min.js}"></script> <script> function login() { var username = $("#username").val(); var password = $("#password").val(); var wam_remember_me = $('input[name="wam_remember_me"]:checked').val(); $.ajax({ url : "/api/v1/login", method : "POST", dataType : "JSON", data : { username : username, password : password, wam_remember_me: wam_remember_me // remember me }, success: function (result) { location.href = result['redirect']; }, error: function (event) { } }) } </script> </body> </html>
查看浏览器中本地存储的数据