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("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>

查看浏览器中本地存储的数据

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值