前言
Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正强大之处在于它可以很容易地扩展以满足定制需求。虽然拦截器、过滤器也可以在一定程度上起到安全认证的作用,但是对码农来说,大量原生的代码及代码冗余,十分的不友好。本文以Spring Security5.6.2版本来对用户认证、授权、注销做个说明。
一、Spring Security的简单介绍
1、到博主写博客的时间为止,Spring Security已经更新到了5.6.2了,作为Spring全家桶之一,其可谓是出身名门,当然新版本增加了一些功能,但也可能会出现版本不兼容的问题。
2、功能
1) 对身份验证和授权的全面和可扩展支持
2) 防止攻击,如会话固定,点击劫持,跨站请求伪造等
3) 集成了Servlet API
4) 与Spring Web MVC的可选集成
5) 还定制了一些功能,比如增加了一些加密算法
二、用户认证与授权
1、导入相关依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
注:导入spring-security依赖后,系统会自动过滤,没有进行登录的话,会自动跳转到Spring Security定制的login界面上 。通过查看源码HttpSecurity.java ,不难发现系统默认自动生成一个登录页面在URL里显示“/login”,认证失败会重定向到“/login?error”,并且登录页面的详细信息可以在formloginconfiguration .loginpage (String)中找到。
那它定制的登录界面,咱咋知道它的登录名和密码呢?你在源码里可以发现大量的hasRole("USER"),登录名就是它了,甚至还可以小写。那密码呢?运行程序后控制台会自动生成一个秘钥,当然人家会提示你:此生成的密码仅供开发使用。在生产环境中运行应用程序之前,必须更新安全配置。
2、授权(Authorization)
学过网络工程的人,可能会对AAA认证机制有所了解,Cisco开发的一个提供网络安全的系统,包括身份验证(Authentication)、授权 (Authorization)和统计 (Accounting)。那Spring Security的授权怎么配置呢?
1)写一个安全配置类:SecurityConfig,让这个类继承WebSecurityConfigurerAdapter,然后重写configure(HttpSecurity http)方法
2)授权与拦截验证
一般而言,首页所有人都能访问,功能页只有对应有权限的人才可以访问。那如何定制授权的规则,拦截非授权的请求呢?阅读源码可知,可以通过HttpSecurity的参数名http调用授权请求,并且授权调用方法的形式类似链式编程。
antMatcher用在多个HttpSecurity的场景下,用来为每个HttpSecurity过滤。没有权限会调用formLogin()方法,回到登录界面 。比如:我在项目里允许访问项目主路径"/"的请求,其它请求都要经过拦截验证,让不同的vip跳到它们的专属页面。
SecurityConfig授权配置如下:
//授权 @Override protected void configure(HttpSecurity http) throws Exception { //首页所有人能访问,功能页只有对应有权限的人才可以访问 //请求授权的规则 http.authorizeHttpRequests().antMatchers("/").permitAll() //允许访问项目主路径"/"的请求,其它请求都要经过拦截验证 .antMatchers("/level1/**").hasRole("vip1") .antMatchers("/level2/**").hasRole("vip2") .antMatchers("/level3/**").hasRole("vip3"); //没有权限默认到登录页面,需要开启登录的页面 http.formLogin(); }
3)结果
配置完成后,系统不会自动跳转到Spring Security定制的login界面上,而是到自己写的主页面index.html上。当然点击不同vip的专属资源,需要进行登录认证。
3、认证 (Authentication)
1)认证重写了configure(AuthenticationManagerBuilder auth)方法,数据正常应该从数据库里面读(auth.jdbcAuthentication()),为了方便,本文是从内存里直接读的(auth.inMemoryAuthentication())从内存读取当然也更快一点了。
2)进行认证配置
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { //认证 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //这些数据正常应该从数据库里面读(auth.jdbcAuthentication()),下面是从内存里读的(auth.inMemoryAuthentication()) auth.inMemoryAuthentication() .withUser("guest") .password("{noop}123456") .roles("vip1") .and().withUser("user")//and()拼接 .password("{noop}55555") .roles("vip1","vip2") .and().withUser("admin") .password("{noop}root").roles("vip1","vip2","vip3"); }
在这里,我们创建了三类用户:
第一类:游客(guest) ,拥有一个查看权限(这里定义为vip1)
第二类:登录用户(user),拥有查看和操作的权限(这里定义为vip1和vip2)
第三类:管理员(admin),拥有查看、操作和管理的权限(这里定义为vip1、vip2和vip3)
使用.and()进行多用户拼接
3)Spring Security5.0+加密方法说明
当进行如下配置时,会出现500错误,并且出现:There is no PasswordEncoder mapped for the id "null",这是由于新版本中一定要指定密码编码(PasswordEncoder)的方式。
在Spring Security5.0+中 新增了很多的加密方法。spring security 5支持的加密 方式有bcrypt、ldap、MD4、MD5、noop、pbkdf2、scrypt、SHA-1、SHA-256、 sha256。
为方便校验测试,{noop}是制定密码加密策略为不加密 。noop的意思是明文保存的密码 (noop: No Operation)。此PasswordEncoder仅用于遗留和测试目的,不被认为是安全的。一个什么都不做的密码编码器,对于使用纯文本密码的测试非常有用。
spring security官方推荐使用更加安全的bcrypt加密方式。该加密方式使用了BCrypt强哈希函数的PasswordEncoder的实现,并且强度参数越大,需要以指数方式对密码进行哈希处理的工作量就越大。
改善之后的代码如下:
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //这些数据正常应该从数据库里面读(auth.jdbcAuthentication()),下面是从内存里读的(auth.inMemoryAuthentication()) auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("guest") .password(new BCryptPasswordEncoder().encode("123456")) .roles("vip1") .and().withUser("user")//and()拼接 .password(new BCryptPasswordEncoder().encode("55555")) .roles("vip1","vip2") .and().withUser("admin") .password(new BCryptPasswordEncoder().encode("root")).roles("vip1","vip2","vip3"); }
4)结果测试
编写一个Controller类
package com.study.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @Controller public class RouterController { @RequestMapping({"/", "/index"}) public String index() { return "index"; } @RequestMapping("/toLogin") public String toLogin() { return "views/login"; } @RequestMapping("/level1/{id}") public String level1(@PathVariable("id") int id) { return "views/level1/" + id; } @RequestMapping("/level2/{id}") public String level2(@PathVariable("id") int id) { return "views/level2/" + id; } @RequestMapping("/level3/{id}") public String level3(@PathVariable("id") int id) { return "views/level3/" + id; } }
结果如下:管理员用户可以访问到vip3下的资源
三、用户注销
1、首先,应该开启注销功能,注销完成之后让它跳转至首页
http.logout().logoutSuccessUrl("/");
2、通过源码注释,可知:HttpSecurity提供注销的支持,在使用WebSecurityConfigurerAdapter时会自动应用。默认情况下,会访问URL“/logout”地址,并使HTTP会话无效、清除配置的任何rememberMe()身份验证、清除SecurityContextHolder,然后重定向到“/login?success”来注销用户。
3、结果
注销之后,成功退到首页