博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringSecurity之记住我功能的实现
阅读量:5321 次
发布时间:2019-06-14

本文共 23194 字,大约阅读时间需要 77 分钟。

Spring security记住我基本原理:

登录的时候,请求发送给过滤器UsernamePasswordAuthenticationFilter,当该过滤器认证成功后,会调用RememberMeService,会生成一个token,将token写入到浏览器cookie,同时RememberMeService里边还有个TokenRepository,将token和用户信息写入到数据库中。这样当用户再次访问系统,访问某一个接口时,会经过一个RememberMeAuthenticationFilter的过滤器,他会读取cookie中的token,交给RememberService,RememberService会用TokenRepository根据token从数据库中查是否有记录,如果有记录会把用户名取出来,再调用UserDetailService根据用户名获取用户信息,然后放在SecurityContext里。

image.png

RememberMeAuthenticationFilter在Spring Security中认证过滤器链的倒数第二个过滤器位置,当其他认证过滤器都没法认证成功的时候,就会调用RememberMeAuthenticationFilter尝试认证。

image.png

实现:

1,登录表单加上,SpringSecurity在SpringSessionRememberMeServices类里定义了一个常量,默认值就是remember-me

2,根据上边的原理图可知,要配置TokenRepository,把生成的token存进数据库,这是一个配置bean的配置,放在了BrowserSecurityConfig里

3,在configure里配置

4,在BrowserProperties里加上自动登录时间,把记住我时间做成可配置的

//记住我秒数配置

private int rememberMeSeconds = 10;

以下是相关的配置

pom.xml:

4.0.0
urity
demo
0.0.1-SNAPSHOT
jar
demo
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
2.0.3.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.apache.tomcat.embed
tomcat-embed-jasper
8.5.12
javax.servlet
javax.servlet-api
3.1.0
javax.servlet
jstl
1.2
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-security
org.slf4j
jcl-over-slf4j
1.7.25
org.projectlombok
lombok
1.16.22
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.2.0
mysql
mysql-connector-java
com.alibaba
druid
1.0.25
org.springframework.social
spring-social-config
org.springframework.social
spring-social-security
org.springframework.social
spring-social-web
org.springframework.security
spring-security-config
5.0.6.RELEASE
org.springframework.boot
spring-boot-maven-plugin

SecurityConfiguration:

package urity.demo.config;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.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.builders.WebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;import javax.annotation.Resource;import javax.sql.DataSource;@Configurationpublic class SecurityConfiguration extends WebSecurityConfigurerAdapter {    @Resource    private DataSource dataSource;    @Resource    private UserDetailsService myUserDetailsService;    /**     * 配置TokenRepository     *     * @return     */    @Bean    public PersistentTokenRepository persistentTokenRepository() {        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();        // 配置数据源        jdbcTokenRepository.setDataSource(dataSource);        // 第一次启动的时候自动建表(可以不用这句话,自己手动建表,源码中有语句的)//         jdbcTokenRepository.setCreateTableOnStartup(true);        return jdbcTokenRepository;    }    // 处理密码加密解密逻辑    @Bean    public PasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();    }    //验证相关    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        super.configure(auth);    }    //浏览器相关    @Override    protected void configure(HttpSecurity http) throws Exception {        http                .authorizeRequests()                .antMatchers("/hello", "/login.html").permitAll()                .anyRequest().authenticated()                .and()                .formLogin()                //指定登录页的路径                .loginPage("/hello")                //指定自定义form表单请求的路径                .loginProcessingUrl("/authentication/form")                .failureUrl("/login?error")                .defaultSuccessUrl("/success")                //必须允许所有用户访问我们的登录页(例如未验证的用户,否则验证流程就会进入死循环)                //这个formLogin().permitAll()方法允许所有用户基于表单登录访问/login这个page。                .permitAll()                .and()                .rememberMe()                                   // 记住我相关配置                .tokenRepository(persistentTokenRepository())                .tokenValiditySeconds(1209600)        ;        //默认都会产生一个hiden标签 里面有安全相关的验证 这边我们不需要 可禁用掉        http.csrf().disable();    }    //web安全相关    @Override    public void configure(WebSecurity web) throws Exception {        super.configure(web);    }}

MyUserDetailService:

package urity.demo.support;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.AuthorityUtils;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.stereotype.Component;import urity.demo.entity.User;import java.util.ArrayList;import java.util.List;import java.util.logging.Logger;//自定义用户处理的逻辑//用户的信息的service@Componentpublic class MyUserDetailService implements UserDetailsService {    /**     * 日志处理类     */    private org.slf4j.Logger logger =  LoggerFactory.getLogger(this.getClass());    @Autowired    private PasswordEncoder passwordEncoder;    /**     * 根据用户名加载用户信息     *     * @param username 用户名     * @return UserDetails     * @throws UsernameNotFoundException     */    @Override    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {        logger.info("表单登录用户名:" + username);        System.out.println("表单登录用户名:" + username);        List
grantedAuthorityList = new ArrayList<>(); grantedAuthorityList.add(new GrantedAuthority() { @Override public String getAuthority() { return "admin"; } }); User user = new User(); user.setUsername("test"); user.setPassword("123"); String pWord =passwordEncoder.encode(user.getPassword()); System.out.println("表单登录加密后密码:" + pWord); System.out.println("库中的username:"+user.getUsername()); if(username.equals(user.getUsername())) { MyUser myUser = new MyUser(user.getUsername(), pWord, true, true, true, true, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_USER")); return myUser; }else { throw new UsernameNotFoundException("用户["+username+"]不存在"); } }}

MyUser:

import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.security.core.CredentialsContainer;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.SpringSecurityCoreVersion;import org.springframework.security.core.authority.AuthorityUtils;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.crypto.factory.PasswordEncoderFactories;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.util.Assert;import java.io.Serializable;import java.util.*;import java.util.function.Function;public class MyUser implements UserDetails, CredentialsContainer {    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;    // ~ Instance fields    // ================================================================================================    private String password;    private final String username;    private final Set
authorities; private final boolean accountNonExpired; private final boolean accountNonLocked; private final boolean credentialsNonExpired; private final boolean enabled; // ~ Constructors // =================================================================================================== /** * Calls the more complex constructor with all boolean arguments set to {@code true}. */ public MyUser(String username, String password, Collection
authorities) { this(username, password, true, true, true, true, authorities); } /** * Construct the
User with the details required by * {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider}. * * @param username the username presented to the *
DaoAuthenticationProvider * @param password the password that should be presented to the *
DaoAuthenticationProvider * @param enabled set to
true if the user is enabled * @param accountNonExpired set to
true if the account has not expired * @param credentialsNonExpired set to
true if the credentials have not * expired * @param accountNonLocked set to
true if the account is not locked * @param authorities the authorities that should be granted to the caller if they * presented the correct username and password and the user is enabled. Not null. * * @throws IllegalArgumentException if a
null value was passed either as * a parameter or as an element in the
GrantedAuthority collection */ public MyUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection
authorities) { if (((username == null) || "".equals(username)) || (password == null)) { throw new IllegalArgumentException( "Cannot pass null or empty values to constructor"); } this.username = username; this.password = password; this.enabled = enabled; this.accountNonExpired = accountNonExpired; this.credentialsNonExpired = credentialsNonExpired; this.accountNonLocked = accountNonLocked; this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities)); } // ~ Methods // ======================================================================================================== public Collection
getAuthorities() { return authorities; } public String getPassword() { return password; } public String getUsername() { return username; } public boolean isEnabled() { return enabled; } public boolean isAccountNonExpired() { return accountNonExpired; } public boolean isAccountNonLocked() { return accountNonLocked; } public boolean isCredentialsNonExpired() { return credentialsNonExpired; } public void eraseCredentials() { password = null; } private static SortedSet
sortAuthorities( Collection
authorities) { Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection"); // Ensure array iteration order is predictable (as per // UserDetails.getAuthorities() contract and SEC-717) SortedSet
sortedAuthorities = new TreeSet
( new MyUser.AuthorityComparator()); for (GrantedAuthority grantedAuthority : authorities) { Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements"); sortedAuthorities.add(grantedAuthority); } return sortedAuthorities; } private static class AuthorityComparator implements Comparator
, Serializable { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; public int compare(GrantedAuthority g1, GrantedAuthority g2) { // Neither should ever be null as each entry is checked before adding it to // the set. // If the authority is null, it is a custom authority and should precede // others. if (g2.getAuthority() == null) { return -1; } if (g1.getAuthority() == null) { return 1; } return g1.getAuthority().compareTo(g2.getAuthority()); } } /** * Returns {@code true} if the supplied object is a {@code User} instance with the * same {@code username} value. *

* In other words, the objects are equal if they have the same username, representing * the same principal. */ @Override public boolean equals(Object rhs) { if (rhs instanceof MyUser) { return username.equals(((MyUser) rhs).username); } return false; } /** * Returns the hashcode of the {@code username}. */ @Override public int hashCode() { return username.hashCode(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(super.toString()).append(": "); sb.append("Username: ").append(this.username).append("; "); sb.append("Password: [PROTECTED]; "); sb.append("Enabled: ").append(this.enabled).append("; "); sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; "); sb.append("credentialsNonExpired: ").append(this.credentialsNonExpired) .append("; "); sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; "); if (!authorities.isEmpty()) { sb.append("Granted Authorities: "); boolean first = true; for (GrantedAuthority auth : authorities) { if (!first) { sb.append(","); } first = false; sb.append(auth); } } else { sb.append("Not granted any authorities"); } return sb.toString(); } public static MyUser.UserBuilder withUsername(String username) { return new MyUser.UserBuilder().username(username); } /** * Builds the user to be added. At minimum the username, password, and authorities * should provided. The remaining attributes have reasonable defaults. */ public static class UserBuilder { private String username; private String password; private List

authorities; private boolean accountExpired; private boolean accountLocked; private boolean credentialsExpired; private boolean disabled; /** * Creates a new instance */ private UserBuilder() { } /** * Populates the username. This attribute is required. * * @param username the username. Cannot be null. * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) */ private MyUser.UserBuilder username(String username) { Assert.notNull(username, "username cannot be null"); this.username = username; return this; } /** * Populates the password. This attribute is required. * * @param password the password. Cannot be null. * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) */ public MyUser.UserBuilder password(String password) { Assert.notNull(password, "password cannot be null"); this.password = password; return this; } /** * Populates the roles. This method is a shortcut for calling * {@link #authorities(String...)}, but automatically prefixes each entry with * "ROLE_". This means the following: * *
* builder.roles("USER","ADMIN"); * * * is equivalent to * *
* builder.authorities("ROLE_USER","ROLE_ADMIN"); * * *

* This attribute is required, but can also be populated with * {@link #authorities(String...)}. *

* * @param roles the roles for this user (i.e. USER, ADMIN, etc). Cannot be null, * contain null values or start with "ROLE_" * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) */ public MyUser.UserBuilder roles(String... roles) { List
authorities = new ArrayList
( roles.length); for (String role : roles) { Assert.isTrue(!role.startsWith("ROLE_"), role + " cannot start with ROLE_ (it is automatically added)"); authorities.add(new SimpleGrantedAuthority("ROLE_" + role)); } return authorities(authorities); } /** * Populates the authorities. This attribute is required. * * @param authorities the authorities for this user. Cannot be null, or contain * null values * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) * @see #roles(String...) */ public MyUser.UserBuilder authorities(GrantedAuthority... authorities) { return authorities(Arrays.asList(authorities)); } /** * Populates the authorities. This attribute is required. * * @param authorities the authorities for this user. Cannot be null, or contain * null values * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) * @see #roles(String...) */ public MyUser.UserBuilder authorities(List
authorities) { this.authorities = new ArrayList
(authorities); return this; } /** * Populates the authorities. This attribute is required. * * @param authorities the authorities for this user (i.e. ROLE_USER, ROLE_ADMIN, * etc). Cannot be null, or contain null values * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) * @see #roles(String...) */ public MyUser.UserBuilder authorities(String... authorities) { return authorities(AuthorityUtils.createAuthorityList(authorities)); } /** * Defines if the account is expired or not. Default is false. * * @param accountExpired true if the account is expired, false otherwise * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) */ public MyUser.UserBuilder accountExpired(boolean accountExpired) { this.accountExpired = accountExpired; return this; } /** * Defines if the account is locked or not. Default is false. * * @param accountLocked true if the account is locked, false otherwise * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) */ public MyUser.UserBuilder accountLocked(boolean accountLocked) { this.accountLocked = accountLocked; return this; } /** * Defines if the credentials are expired or not. Default is false. * * @param credentialsExpired true if the credentials are expired, false otherwise * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) */ public MyUser.UserBuilder credentialsExpired(boolean credentialsExpired) { this.credentialsExpired = credentialsExpired; return this; } /** * Defines if the account is disabled or not. Default is false. * * @param disabled true if the account is disabled, false otherwise * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) */ public MyUser.UserBuilder disabled(boolean disabled) { this.disabled = disabled; return this; } public UserDetails build() { return new User(username, password, !disabled, !accountExpired, !credentialsExpired, !accountLocked, authorities); } } }

login.html:

    
第一个HTML页面
Title自定义表单验证:
用户名:
密码:
记住我

LoginController:

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import urity.demo.entity.User;@Controllerpublic class LoginController {    @RequestMapping("/hello")    public String hello() {        System.out.println("kkkk==");        return "login";    }    @RequestMapping("/success")    public String success(){        return "success";    }    @RequestMapping("/forkl")    public String check(User user){        System.out.println(user);        return "success";    }    @RequestMapping("/user")    public String fuinduser(){       return "user";    }}

user.html:

    
Title由于用了记住我 所以现在可以直接访问了哦!

到此我们来启动项目,首次访问http://localhost:8787/user会需要我们登录,这里我们进行登录先不勾选记住我:

image.png

登录成功后可以正常访问user,然后我们关闭浏览器重新打开 访问http://localhost:8787/user会被返回到登录的页面,这个就是没有任何效果的演示.

然后我们再次登录,并勾选记住我:

image.png

这里我们登录成功后关闭浏览器再打开 仍然可以访问http://localhost:8787/user,而且不需要登录:

image.png

这里浏览器做了如下的事情:

  • 在我们数据库建立表并插入数据

image.png

  • 然后我们关闭浏览器在访问,它会去库里面查找响应的token,如果有就不用登录直接访问:

image.png

到此,rememberme的功能就完成了

转载于:https://www.cnblogs.com/charlypage/p/9332984.html

你可能感兴趣的文章
Ubunt中文乱码
查看>>
《当幸福来敲门》读后
查看>>
python3基础知识自学笔记3-集合字典元组
查看>>
session的属性/方法/事件
查看>>
【转】系统无法进入睡眠模式解决办法
查看>>
思维导图 第六章 项目进度管理
查看>>
[Tex学习笔记]尝试数学公式
查看>>
省市县,循环组装,整合大数组
查看>>
C语言中返回字符串函数的四种实现方法
查看>>
Jmeter学习及使用(一)安装
查看>>
H5 调用手机摄像机、相册功能
查看>>
Google Closure Compiler 高级模式及更多思考(转)
查看>>
python--闭包函数、装饰器
查看>>
【坑】linux目录软连接的相关操作--很容易误操作
查看>>
Phpstorm中使用SFTP
查看>>
stm32中字节对齐问题(__align(n),__packed用法)
查看>>
like tp
查看>>
开启一个项目如何上传到git
查看>>
使用 github Pages 服务建立个人独立博客全过程
查看>>
posix多线程有感--线程高级编程(线程属性函数总结)(代码)
查看>>