配置 Spring Security
1.继承 WebSecurityConfigurerAdapter
通过重写抽象接口 WebSecurityConfigurerAdapter,再加上注解 @EnableWebSecurity,可以实现 Web 的安全配置。
WebSecurityConfigurerAdapterConfig 模块一共有 3 个 builder(构造程序)。
AuthenticationManagerBuilder
:认证相关 builder,用来配置全局的认证相关的信息。它包含AuthenticationProvider
和UserDetailsService
,前者是 认证服务提供者,后者是 用户详情查询服务。WebSecurity
:进行全局请求忽略规则配置、HttpFirewall 配置、debug 配置、全局 SecurityFilterChain 配置。HttpSecurity
:进行权限控制规则相关配置。
配置安全,通常要重写以下方法:
// 通过 auth 对象的方法添加身份验证protectedvoidconfigure(AuthenticationManagerBuilder auth)throwsException{}// 通常用于设置忽略权限的静态资源publicvoidconfigure(WebSecurity web)throwsException{}// 通过 HTTP 对象的 authorizeRequests() 方法定义 URL 访问权限。默认为 formLogin() 提供一个简单的登录验证页面protectedvoidconfigure(HttpSecurity httpSecurity)throwsException{}
2.配置自定义策略
配置安全需要继承 WebSecurityConfigurerAdapter,然后重写其方法,见以下代码:
packagecom.example.demo.config;// 指定为配置类@Configuration// 指定为 Spring Security 配置类,如果是 WebFlux,则需要启用@EnableWebFluxSecurity @EnableWebSecurity// 如果要启用方法安全设置,则开启此项。@EnableGlobalMethodSecurity(prePostEnabled =true)publicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{@Overridepublicvoidconfigure(WebSecurity web)throwsException{//不拦截静态资源
web.ignoring().antMatchers("/static/**");}@BeanpublicPasswordEncoderpasswordEncoder(){// 使用 BCrypt 加密returnnewBCryptPasswordEncoder();}@Overrideprotectedvoidconfigure(HttpSecurity http)throwsException{
http.formLogin().usernameParameter("uname").passwordParameter("pwd").loginPage("/admin/login").permitAll().and().authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN")// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();
http.logout().permitAll();
http.rememberMe().rememberMeParameter("rememberme");// 处理异常,拒绝访问就重定向到 403 页面
http.exceptionHandling().accessDeniedPage("/403");
http.logout().logoutSuccessUrl("/");
http.csrf().ignoringAntMatchers("/admin/upload");}}
formLogin()
:自定义用户登录验证的页面。表示开启表单登录配置,loginPage
用来配置登录页面地址;usernameParameter
表示登录用户名的参数名称;passwordParameter
表示登录密码的参数名称;permitAll
表示跟登录相关的页面和接口不做拦截,直接通过。需要注意的是usernameParameter
、passwordParameter
需要和login.html
中登录表单的配置一致。authorizeRequests()
:定义哪些 URL 需要被保护,哪些不需要被保护。antMatchers("/admin/**").hasRole("ADMIN")
:定义/admin/
下的所有 URL。只有拥有ADMIN
角色的用户才有访问权限。.logout().permitAll()
:表示在 Spring Security 配置中,无论用户是否经过身份验证,都可以访问注销(logout
)页面。这意味着,即使是一个未经过身份验证的用户也可以访问注销功能,这在某些情况下可能是一个安全隐患。因此,这个配置通常用于开发环境或测试环境,以确保用户可以轻松地测试注销功能,而在生产环境中,出于安全考虑,通常会更加限制对注销页面的访问权限。http.csrf()
:配置是否开启 CSRF 保护,还可以在开启之后指定忽略的接口。and()
:会返回 HttpSecurityBuilder 对象的一个子类(实际上就是 HttpSecurity),所以and()
方法相当于又回到 HttpSecurity 实例,重新开启新一轮的配置。在 Spring Security 的配置中,.and()
方法用于将多个安全配置连接起来,形成一个连续的配置链。这种方法允许开发者逐步添加和配置安全策略,确保每个配置步骤都是基于前一个步骤的结果进行的。通过使用.and()
方法,可以构建复杂的安全配置,包括身份验证、授权、安全防护等。例如,在配置 Spring Security 时,可能会先设置用户认证方式,然后使用.and()
方法连接到授权配置,接着可能再次使用.and()
方法连接到 CSRF 保护等高级配置。这种链式配置方式使得代码更加清晰和易于管理。
http
.someConfigurer
.<some feature of configurer>().<some feature of configurer>().and().someOtherConfigurer
.<some feature of someOtherConfigurer>()....and()...
如果开启了 CSRF,则一定在验证页面加入以下代码以传递
token
值:
<head><metaname="_csrf"th:content="${_csrf.token}"/><!-- default header name is X-CSRF-TOKEN --><metaname="_csrf_header"th:content="${_csrf.headerName}"/></head>
如果要提交表单,则需要在表单中添加以下代码以提交
token
值:
<inputtype="hidden"th:name="${_csrf.parameterName)"th:value="${_csrf.token)">
http.rememberMe()
:“记住我” 功能,可以指定参数。
<inputclass="i-checks"type="checkbox"name="rememberme"/> 记住我
3.配置加密方式
默认的加密方式是 BCrypt。只要在安全配置类配置即可使用,见以下代码:
@BeanpublicPasswordEncoderpasswordEncoder(){// 使用 BCrypt 加密returnnewBCryptPasswordEncoder();}
在业务代码中,可以用以下方式对密码迸行加密:
BCryptPasswordEncoder encoder =newBCryptPasswordEncoder();String encodePassword = encoder.encode(password);
3.1 BCrypt 加密
BCrypt 是一种基于哈希函数的加密算法,它使用一个 密码 和一个 盐值 作为输入,生成一个固定长度的 密码哈希值。这个哈希值在每次密码输入时都会重新生成,而且会随着盐值的改变而改变。BCrypt 的盐值是一个随机生成的字符串,与密码一起用于哈希函数中,使得相同的密码在每次加密时都会生成不同的哈希值。
BCrypt 的另一个重要特点是它使用了一个加密算法来混淆密码哈希值。这个加密算法使用一个密钥和一个初始化向量(
IV
)来加密密码和盐值。加密后的数据被存储在数据库中,用于后续的密码验证。
BCrypt 的加密过程可以分为以下几个步骤:
- 生成盐值:BCrypt 使用一个随机数生成器生成一个随机的 盐值。这个盐值是一个随机的字符串,用于与密码一起生成哈希值。
- 混合盐值和密码:将 密码 和 盐值 混合在一起,然后使用一个 哈希函数 生成一个固定长度的 哈希值。
- 加密哈希值:使用一个 加密算法 将哈希值混淆,生成一个 加密的哈希值。这个加密的哈希值被存储在数据库中。
- 验证密码:在验证密码时,用户输入 密码,系统使用相同的 盐值、哈希函数 和 加密算法 生成一个 新的哈希值。然后,将新的哈希值与数据库中的加密哈希值进行比较,如果它们匹配,则密码验证成功。
🚀 BCrypt 加密/匹配工具 - 在线密码哈希生成与匹配
4.自定义加密规则
除默认的加密规则,还可以自定义加密规则。具体见以下代码:
@Overrideprotectedvoidconfigure(AuthenticationManagerBuilder auth)throwsException(
auth.userDetailsService(UserService()).passwordEncoder(newPasswordEncoder(){@OverridepublicStringencode(CharSequence charSequence){returnMD5Util.encode((String) charSequence);}@Overridepublicbooleanmatches(CharSequence charSequence,String s){return s.equals(MD5Util.encode((String) charSequence));}});}
5.配置多用户系统
一个完整的系统一般包含多种用户系统,比如 “后台管理系统 + 前端用户系统"。Spring Security 默认只提供一个用户系统,所以,需要通过配置以实现多用户系统。
比如,如果要构建一个前台会员系统,则可以通过以下步骤来实现。
5.1 构建 UserDetailsService 用户信息服务接口
构建前端用户 UserSecurityService 类,并继承 UserDetailsService。具体见以下代码:
publicclassUserSecurityServiceimplementsUserDetailsService{@AutowiredprivateUserRepository userRepository;@OverridepublicUserDetailsloadUserByUsername(String name)throwsUsernameNotFoundException{User user = userRepository.findByName(name);if(user ==null){User mobileUser = userRepository.findByMobile(name);if(mobileUser ==null){User emailUser = userRepository .findByEmail(name);if(emailUser ==null){thrownewUsernameNotFoundException("用户名,邮箱或手机号不存在!");}else{
user = userRepository.findByEmail(name);}}else{
user = userRepository.findByMobile(name);}}elseif("locked".equals(user.getStatus())){//被锁定,无法登录thrownewLockedException("用户被锁定”);}return user;}}
5.2 进行安全配置
在继承 WebSecurityConfigurerAdapter 的 Spring Security 配置类中,配置 UserSecurityService 类。
@BeanUserDetailsServiceUserService(){returnnewUserSecurityService();}
如果要加入后台管理系统,则只需要重复上面步骤即可。
6.获取当前登录用户信息的几种方式
获取当前登录用户的信息,在权限开发过程中经常会遇到。而对新人来说,不太了解怎么获取,经常遇到获取不到或报错的问题。
所以,本节讲解如何在常用地方获取当前用户信息。
6.1 在 Thymeleaf 视图中获取
要 Thymeleaf 视图中获取用户信息,可以使用 Spring Security 的标签特性。
在 Thymeleaf 页面中引入 Thymeleaf 的 Spring Security 依赖,见以下代码:
<!DOCTYPEhtml>
<html lang="zh" xmlns:th=”http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<!-- 省略 --><body><!-- 匿名 --><divsec:authorize="isAnonymous()">
未登录,单击 <ath:href="@{/home/login}">登录</a></div><!-- 已登录 --><divsec:authorize="isAuthenticated()"><p>已登录</p><p>登录名: <spansec:authentication="name"></span></p><p>角色: <spansec:authentication="principal.authorities"></span></p><p>id: <spansec:authentication="principal.id"></span></p><p>Username: <spansec:authentication="principal.username"></span></p></div></body></html>
这里要特别注意版本的对应。如果引入了
thymeleaf-extras-springsecurity
依赖依然获取不到信息,那么可能是 Thymeleaf 版本和
thymeleaf-extras-springsecurity
的版本不对。
请检查在
pom.xml
文件的两个依赖,见以下代码:
<dependency><groupld>org.springframework.boot</groupld><artifactld>spring-boot-starter-thymeleaf</artifactld></dependency><dependency><groupld>org.thymeleaf.extras</groupld><artifactld>thymeleaf-extras-springsecurity5</artifactld></dependency>
6.2 在 Controller 中获取
在控制器中获取用户信息有 3 种方式,见下面的代码注释。
@GetMapping("userinfo")publicStringgetProduct(Principal principal,Authentication authentication,HttpServletRequest httpServletRequest){/**
* Description: 1.通过 Principal 参数获取
*/String username = principal.getName();/**
* Description: 2.通过 Authentication 参数获取
*/String userName2 = authentication.getName();/**
* Description: 3.通过 HttpServletRequest 获取
*/Principal httpServletRequestUserPrincipal = httpServletRequest.getUserPrincipal();
String userName3 = httpServletRequestUserPrincipal.getName();return username;}
6.3 在 Bean 中获取
在 Bean 中,可以通过以下代码获取:
Authentication authentication =SecurityContextHolder.getContext().getAuthentication();if(!(authentication instanceofAnonymousAuthenticationToken)){String username = authentication.getName();return username;}
在其他 Authentication 类也可以这样获取。比如在 UsernamePasswordAuthenticationToken 类中。
如果上面代码获取不到,并不是代码错误,则可能是因为以下原因造成的:
- ⭕ 要使上面的获取生效,必须在继承 WebSecurityConfigurerAdapter 的类中的
http.antMatcher("/*")
的鉴权 URI 范围内。 - ⭕ 没有添加 Thymeleaf 的
thymeleaf-extras-springsecurity
依赖。 - ⭕ 添加了 Spring Security 的依赖,但是版本不对,比如 Spring Security 和 Thymeleaf 的版本不对。
版权归原作者 G皮T 所有, 如有侵权,请联系我们删除。