上一篇文章介绍了spring security的简单使用,这一章介绍自动登录的简单实现。
spring security自动登录是通过登录时生成并保存相关加密串,在下次登录失效时再访问时直接从拉取保存的加密串进行自动登录验证来实现,再次访问不需要再显式地进行用户名密码登录。框架中自动登录加密串存储主要基于关系型数据库实现。
目录
2. 配置WebSecurityConfigurerAdapter
一、实现原理
1. 加密串存储
在认证成功之后将加密串分别保存到浏览器的cookies中和数据库表中。
2. 再次访问
当再次访问时,获取cookies信息,拿着cookies中的加密串到数据库进行比对,如果比对成功并查询到对应信息,认证成功,可以登录。
* 自动登录过程框架内部已经实现
示意图:
二、具体实现
1. 创建数据表
表结构必须与框架中要求的结构一致。框架中实现了自动建表功能,但是出于安全考虑,系统中配置的数据库账号一般要求不具备建表权限,建议手动建表。
表结构按照JdbcTokenRepositoryImpl建表语句:
2. 配置WebSecurityConfigurerAdapter
基于上一篇配置做拓展。
配置自动登录需要注入数据源和数据库操作对象,在配置中开启remember me自动登录,并设置自动登录Token过期时间和用户权限等用户基本信息来源业务类。
(1)准备(见附录)
(2)注入数据源
@Autowired
private DataSource dataSource;
(3)注入操作数据库对象
/**
* 自动登录Token仓储
* @return PersistentTokenRepository
*/
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
// 自动建表,不建议开启
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
(4)配置自动登录
@Override
protected void configure(HttpSecurity http) throws Exception {
... ...
// 自动登录
http.rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(120)
.userDetailsService(userDetailService);
}
3. 添加自动登录复选框
在登录页面配置自动登录复选框,框架要求以“remember-me”做name。
<input type="checkbox" name="remember-me"/>自动登录
(1)查看效果:
登录后Cookies有了remember-me:
数据库添加了记录:
自行勾选自动登录后重启系统,重新访问要求登录的页面/接口以验证。
(2)查看勾选前后访问参数区别
不勾选(无remember-me参数):
勾选(有remember-me参数):
这可以为后续前后端完全分离项目自动登录提供思路。
附录
基于第一篇配置做拓展。
添加依赖:
<properties> ... ... <spring.boot.version>2.2.1.RELEASE</spring.boot.version> <druid.version>1.1.10</druid.version> <mysql.version>5.1.46</mysql.version> <mybatis.version>1.3.2</mybatis.version> </properties> <dependencies> ... ... <!-- jdbc --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> <version>${spring.boot.version}</version> </dependency> <!-- druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <!-- mysql connector --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.version}</version> </dependency> </dependencies>
application.yml添加数据源和mybatis配置:
spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/security_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true username: root password: root initialSize: 5 #初始化连接大小 minIdle: 5 #最小连接池数量 maxActive: 20 #最大连接池数量 maxWait: 60000 #获取连接时最大等待时间,单位毫秒 timeBetweenEvictionRunsMillis: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 minEvictableIdleTimeMillis: 300000 #配置一个连接在池中最小生存的时间,单位是毫秒 validationQuery: SELECT 1 from DUAL #测试连接 testWhileIdle: true #申请连接的时候检测,建议配置为true,不影响性能,并且保证安全性 testOnBorrow: false #获取连接时执行检测,建议关闭,影响性能 testOnReturn: false #归还连接时执行检测,建议关闭,影响性能 poolPreparedStatements: false #是否开启PSCache,PSCache对支持游标的数据库性能提升巨大,oracle建议开启,mysql下建议关闭 maxPoolPreparedStatementPerConnectionSize: 20 #开启poolPreparedStatements后生效 filters: stat,wall,slf4j #配置扩展插件,常用的插件有=>stat:监控统计 log4j:日志 wall:防御sql注入 connectionProperties: 'druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000' #通过connectProperties属性来打开mergeSql功能;慢SQL记录 mybatis: configuration: map-underscore-to-camel-case: true #开启驼峰命名,l_name -> lName jdbc-type-for-null: NULL lazy-loading-enabled: true aggressive-lazy-loading: true cache-enabled: true #开启二级缓存 call-setters-on-nulls: true #map空列不显示问题 mapper-locations: - classpath:mybatis/*.xml
druid连接池:
@Configuration public class DruidConfiguration { @Bean @Primary @ConfigurationProperties(prefix = "spring.datasource") public DataSource dataSource() { return new DruidDataSource(); } @Bean public ServletRegistrationBean<StatViewServlet> statViewServlet() { ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*"); //设置ip白名单 servletRegistrationBean.addInitParameter("allow", "127.0.0.1"); //设置ip黑名单,优先级高于白名单 servletRegistrationBean.addInitParameter("deny", "192.168.0.19"); //设置控制台管理用户 servletRegistrationBean.addInitParameter("loginUsername", "root"); servletRegistrationBean.addInitParameter("loginPassword", "root"); //是否可以重置数据 servletRegistrationBean.addInitParameter("resetEnable", "false"); return servletRegistrationBean; } @Bean public FilterRegistrationBean<WebStatFilter> statFilter() { //创建过滤器 FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(new WebStatFilter()); //设置过滤器过滤路径 filterRegistrationBean.addUrlPatterns("/*"); //忽略过滤的形式 filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); return filterRegistrationBean; } }