目录
1、Spring Security介绍
2、认证授权入门
2.2.1 创建认证服务工程
1、部署认证服务工程
2、配置Spring Security所需要的依赖
3、初始工程自带了一个Controller类,如下
4、需要进行安全配置。
3、授权测试
1、配置用户拥有哪些权限。
2、指定资源与权限的关系。
4、工作原理
Spring Security的执行流程如下:
认证功能几乎是每个项目都要具备的功能,并且它与业务无关,市面上有很多认证框架,如:Apache Shiro、CAS、Spring Security等。由于本项目基于Spring Cloud技术构建,Spring Security是spring家族的一份子且和Spring Cloud集成的很好,所以本项目选用Spring Security作为认证服务的技术框架。
Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架,它是一个专注于为 Java 应用程序提供身份验证和授权的框架。
项目主页:https://spring.io/projects/spring-security
Spring cloud Security: https://spring.io/projects/spring-cloud-security
下边我们使用Spring Security框架快速构建认证授权功能体系。
创建一个xuecheng-plus-auth工程到自己的工程目录下。
此工程是一个普通的spring boot工程,可以连接数据库。
此工程不具备认证授权的功能。
org.springframework.cloud spring-cloud-starter-security org.springframework.cloud spring-cloud-starter-oauth2
package com.xuecheng.auth.controller; import com.xuecheng.ucenter.mapper.XcUserMapper; import com.xuecheng.ucenter.model.po.XcUser; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author Mr.Zhang * @version 1.0 * @description 测试controller * @date 2023/3/16 10:25 */ @Slf4j @RestController public class LoginController { @Autowired XcUserMapper userMapper; @RequestMapping("/login-success") public String loginSuccess() { return "登录成功"; } @RequestMapping("/user/{id}") public XcUser getuser(@PathVariable("id") String id) { XcUser xcUser = userMapper.selectById(id); return xcUser; } @RequestMapping("/r/r1") public String r1() { return "访问r1资源"; } @RequestMapping("/r/r2") public String r2(){ return "访问r2资源"; } }
建一个WebSecurityConfig.java到config下需要三部分内容:
1、用户信息
在内存配置两个用户:zhangsan、lisi
zhangsan用户拥有的权限为p1
lisi用户拥有的权限为p2
2、密码方式
暂时采用明文方式
3、安全拦截机制
/r/**开头的请求需要认证
登录成功到成功页面
代码如下:
package com.xuecheng.auth.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.authentication.AuthenticationManager; 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.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; /** * @author Mr.M * @version 1.0 * @description 安全管理配置 * @date 2023/3/16 10:25 */ @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //配置用户信息服务 @Bean public UserDetailsService userDetailsService() { //这里配置用户信息,这里暂时使用这种方式将用户存储在内存中 InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build()); manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build()); return manager; } @Bean public PasswordEncoder passwordEncoder() { // //密码为明文方式 return NoOpPasswordEncoder.getInstance(); // return new BCryptPasswordEncoder(); } //配置安全拦截机制 @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/r/**").authenticated()//访问/r开始的请求需要认证通过 .anyRequest().permitAll()//其它请求全部放行 .and() .formLogin().successForwardUrl("/login-success");//登录成功跳转到/login-success } }
重启工程
1、访问http://localhost:63070/auth/user/52 可以正常访问
2、访问http://localhost:63070/auth/r/r1 显示登录页面
账号zhangsan,密码为123,如果输入的密码不正确会认证失败,输入正确显示登录成功。
为什么http://localhost:63070/auth/user/52 可以正常访问,访问http://localhost:63070/auth/r/r1 显示登录页面?
http.logout().logoutUrl("/logout");配置了退出页面,认证成功后访问/logout可退出登录
访问:http://localhost:63070/auth/login 端口号配自己电脑的我配的端口是63070
用户认证通过去访问系统资源时spring security进行授权控制,判断用户是否有该资源的访问权限,如果有则继续访问,如果没有则拒绝访问。
下边测试授权功能:
在WebSecurityConfig类配置zhangsan拥有p1权限,lisi拥有p2权限。
//配置用户信息服务 @Bean public UserDetailsService userDetailsService() { //这里配置用户信息,这里暂时使用这种方式将用户存储在内存中 InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build()); manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build()); return manager; }
什么是系统的资源?
比如:查询一个用户的信息,用户信息就是系统的资源,要访问资源需要通过URL,所以我们在controller中定义的每个http的接口就是访问资源的接口。
下边在controller中配置/r/r1需要p1权限,/r/r2需要p2权限。
hasAuthority('p1')表示拥有p1权限方可访问。
代码如下:
@RequestMapping("/r/r1") @PreAuthorize("hasAuthority('p1')")//拥有p1权限方可访问 public String r1() { return "访问r1资源"; } @RequestMapping("/r/r2") @PreAuthorize("hasAuthority('p2')")//拥有p2权限方可访问 public String r2(){ return "访问r2资源"; }
现在重启工程。
当访问以/r/开头的url时会判断用户是否认证,如果没有认证则跳转到登录页面,如果已经认证则判断用户是否具有该URL的访问权限,如果具有该URL的访问权限则继续,否则拒绝访问。
例如:
访问/r/r1,使用zhangsan登录可以正常访问,因为在/r/r1的方法上指定了权限p1,zhangsan用户拥有权限p1,所以可以正常访问。
访问/r/r1,使用lisi登录则拒绝访问,由于lisi用户不具有权限p1需要拒绝访问
注意:如果访问上不加@PreAuthorize,此方法没有授权控制。
整理授权的过程见下图所示:
通过测试认证和授权两个功能,我们了解了Spring Security的基本使用方法,下边了解它的工作流程。
Spring Security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。根据前边知识的学习,可以通过Filter或AOP等技术来实现,Spring Security对Web资源的保护是靠Filter实现的,所以从这个Filter来入手,逐步深入Spring Security原理。
当初始化Spring Security时,会创建一个名为SpringSecurityFilterChain的Servlet过滤器,类型为 org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此类,下图是Spring Security过虑器链结构图:
FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器(AccessDecisionManager)进行处理。
spring Security功能的实现主要是由一系列过滤器链相互配合完成。
下面介绍过滤器链中主要的几个过滤器及其作用:
SecurityContextPersistenceFilter 这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截器),会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext,然后把它设置给 SecurityContextHolder。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好的 SecurityContextRepository,同时清除 securityContextHolder 所持有的 SecurityContext;
UsernamePasswordAuthenticationFilter 用于处理来自表单提交的认证。该表单必须提供对应的用户名和密码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,这些都可以根据需求做相关改变;
FilterSecurityInterceptor 是用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问,前面已经详细介绍过了;
ExceptionTranslationFilter 能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常:AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出。