SpringBoot集成SpringSecurity(二) 個性化登錄配置(remember-me mongodb)

前言

本文件所記錄的是使用SpringSecurity實現remember me功能,有興趣的朋友可以繼續閱讀,有何不足之處還請各位指出(本文未對用戶 -  角色 - 權限三者的關係進行詳細介紹詳情見SpringBoot集成SpringSecurity(一) 初識SpringSecurity

源碼地址:https://github.com/DomeTan/spring_security_02

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

實體類主要包括

  1. User.java  :用戶實體
  2. Role.java :角色實體類
  3. 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>

查看瀏覽器中本地存儲的數據

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章