前言
本文件所記錄的是使用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("[email protected]");
user.setRoles(List.of(userRole));
userRepository.insert(user);
var admin = new User();
admin.setUsername("admin");
admin.setPassword(DigestUtils.md5Hex("123admin"));
admin.setEmail("[email protected]");
admin.setRoles(List.of(adminRole));
userRepository.insert(admin);
var common = new User();
common.setUsername("common");
common.setPassword(DigestUtils.md5Hex("123common"));
common.setEmail("[email protected]");
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" οnclick="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 οnclick="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" οnclick="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>
查看瀏覽器中本地存儲的數據