目录
????????安全框架是对访问权限进行控制的系统架构,旨在确保应用的安全性。应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。
????????用户认证是验证某个用户是否为系统中的合法主体,即用户能否访问该系统。一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证过程。
????????用户授权是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。例如,对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。
????????安全框架的选择也会影响系统的安全性。例如,Shiro比Spring Security更容易使用,实现上简单一些,同时基本的授权认证Shiro也基本够用。而Spring Security社区支持度更高,社区的亲儿子,支持力度和更新维护上有优势,同时和Spring这一套的结合较好。
????????Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI和AOP功能。Spring Security为基于J2EE企业应用软件提供了全面安全服务,包括认证、授权、加密和会话管理等。它对Web安全性的支持大量地依赖于Servlet过滤器。Spring Security采用“安全层”的概念,使每一层都尽可能安全,连续的安全层可以达到全面的防护。它可以在Controller层、Service层、DAO层等以加注解的方式来保护应用程序的安全。Spring Security提供的是应用程序层的安全解决方案,一个系统的安全还需要考虑传输层和系统层的安全,例如采用Htps协议、服务器部署防火墙等。
权限框架一般包含两大核心模块:认证(Authentication)和鉴权(Authorization)。
认证:认证模块负责验证用户身份的合法性,生成认证令牌,并保存到服务端会话中(如TLS)。
鉴权:鉴权模块负责从服务端会话内获取用户身份信息,与访问的资源进行权限比对。
核心组件介绍:
AuthenticationManager
:管理身份验证,可以从多种身份验证方案中选择一种。
Authentication
:用于验证用户的身份。
SecurityContextHolder
:用于管理 SecurityContext
的 ThreadLocal
,以便在整个请求上下文中进行访问,方便用户访问。
AccessDecisionManager
:负责对访问受保护的资源的请求进行决策(即决定是否允许用户访问资源)
AccessDecisionVoter
:是AccessDecisionManager的实现组件之一,它用于对用户请求的访问受保护的资源所需要的角色或权限进行投票。
ConfigAttribute
:用于表示受保护资源或URL需要的访问权限,它可以理解为是访问控制策略的一部分
总结:
????????Spring Security的基本原理是基于J2EE的拦截器原理,形成一条很长的过滤器链。当客户端发起请求时,请求会进入Spring Security的过滤器链,这个过程会判断是否为登录请求。如果是登录请求,则进行用户认证,验证用户是否为系统中的合法主体;如果不是登录请求,则进行用户授权,验证用户是否有权限执行某个操作。在过滤器链中,会根据URI找到对应的鉴权管理器,进行鉴权。
????????Spring Security还采用了责任链的设计模式,通过实现一些拦截器来对访问的URL进行拦截,通过对缓存中的角色进行相应判定后决定是否能访问该URL。
兼容性强:Spring Security
是一个流行的开源框架,它可以与Spring应用程序完美集成。由于它的兼容性很好,因此可以非常方便地使用它保护Web应用程序。
功能强大:Spring Security
具备众多功能,包括注销、登录、角色、权限、令牌、XSS
防御、CSRF
防御等等。它还支持各种身份验证、角色和权限管理方式,如基于表单的认证、基于记住我功能的认证以及OAuth
认证等等。
安全可靠:Spring Security
具有极高的安全性,它使用最新的安全标准和协议来保护Web应用程序。Spring Security
采用安全性分层的策略来保护应用程序中的各个层,例如Web
层、Service
层、DAO
层等等。除此之外,Spring Security
还支持自定义安全策略和事件响应,从而使得开发者可以根据应用程序需求定制安全保护。
易于使用:Spring Security
提供了一种高度简化的方式来保护Web应用程序。它使用简单的标签和安全注解来添加安全保护,从而使得开发者可以基本不需要手动编写代码就可以完成安全保护。
社区广泛:Spring Security
是一个著名的开源框架,因此它有一个庞大的用户社区。这个社区不仅提供了大量的文档、示例和教程,而且还会解答开发者的问题、修复框架中的BUG等等。这为开发者提供了无限的支持和帮助,从而可以使用Spring Security
更加自信。
搭建一个Spring Boot的项目,导入相关依赖。
导入我们的security依赖。
<!-- security--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- freemarker --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency>
spring-boot-starter-security
包含了以下几个主要的依赖:
spring-security-core:
Spring Security
的核心模块,提供了基于权限的访问控制以及其他安全相关功能。spring-security-config:提供了
Spring Security
的配置实现,例如通过Java配置创建安全策略和配置Token存储等。spring-security-web:提供了
Spring Security Web
的基本功能,例如Servlet
集成和通过HttpSecurity
配置应用程序安全策略。
配置application.yml文件
spring: freemarker: # 设置freemarker模板后缀 suffix: .ftl # 设置freemarker模板前缀 template-loader-path: classpath:/templates/ enabled: true
创建
HellController
类,并定义hello请求处理方法:@Controller public class HelloController { @RequestMapping("/hello") public String hello(){ return "hello,spring security"; } }
????????在未配置spring security依赖之前,直接启动项目是可以无阻碍的任意访问请求路径的;但是配置了spring security之后需要通过认证登录后才能访问请求路径。
启动项目,系统会自动生成一个默认的随机登录密码(因为当前没有配置用户信息,配置之后就不会在生成默认登录密码)
这就是 Spring Security
为默认用户 user 生成的临时密码,是一个 UUID 字符串。
在
application.yml
文件中配置自定义用户名和密码。spring: security: ? user: ? ? name: admin ? ? password: 123456
配置完成之后,请重启服务进行测试。
HttpSecurity
是Spring Security
的一个核心类,用于配置应用程序的安全策略。
HttpSecurity
类通常包含许多方法,可以用于配置以下内容:
HTTP 请求的安全策略,例如访问控制、跨站点请求伪造 (CSRF) 防护等。
HTTP 验证的安全策略,例如基于表单、HTTP 基本身份验证、OAuth 等。
访问受保护资源时所需的身份验证和授权方式。
方法 | 说明 |
---|---|
authorizeRequests() | 用于配置如何处理请求的授权,默认情况下所有的请求都需要进行认证和授权才能访问受保护的资源 |
formLogin() | 用于配置基于表单的身份验证,包括自定义登录页面、登录请求路径、用户名和密码的参数名称、登录成功和失败的跳转等。 |
httpBasic() | 用于配置基于HTTP Basic 身份验证,包括定义使用的用户名和密码、realm 名称等。 |
logout() | 用于配置退出登录功能,包括定义退出登录请求的URL、注销成功后的跳转URL、清除会话、删除Remember-Me 令牌等。 |
csrf() | 用于配置跨站请求伪造保护,包括定义CSRF Token 的名称、保存方式、忽略某些请求等。 |
sessionManagement() | 用于配置会话管理,包括定义并发控制、会话失效、禁用URL重定向、会话固定保护等。 |
rememberMe() | 用于配置Remember-Me 功能,包括定义Remember-Me 令牌的名称、有效期、加密方法、登录成功后的处理方式等。 |
exceptionHandling() | 用于配置自定义的异常处理,包括定义异常处理器和异常处理页面等。 |
headers() | 用于配置HTTP响应头信息,包括定义X-Content-Type-Options、X-XSS-Protection、Strict-Transport-Security 等头信息。 |
cors() | 用于配置跨域资源共享,包括定义可访问的来源、Headers 等。 |
addFilter() | 用于向当前HttpSecurity 中添加自定义的Filter 。 |
and() | 用于在配置中添加另一个安全规则,并将两个规则合并。 |
匹配规则:
URL匹配
方法 | 说明 |
---|---|
requestMatchers() | 配置一个request Mather 数组,参数为RequestMatcher 对象,其match 规则自定义,需要的时候放在最前面,对需要匹配的的规则进行自定义与过滤 |
authorizeRequests() | URL权限配置 |
antMatchers() | 配置一个request Mather 的string 数组,参数为ant 路径格式, 直接匹配url |
anyRequest() | 匹配任意url ,无参 ,最好放在最后面 |
保护URL
方法 | 说明 |
---|---|
authenticated() | 保护Url ,需要用户登录 |
permitAll() | 指定URL无需保护,一般应用与静态资源文件 |
hasRole(String role) | 限制单个角色访问 |
hasAnyRole(String… roles) | 允许多个角色访问 |
access(String attribute) | 该方法使用 SPEL , 所以可以创建复杂的限制 |
hasIpAddress(String ipaddressExpression) | 限制IP 地址或子网 |
登录formLogin
方法 | 说明 |
---|---|
loginPage() | 设置登录页面的 URL |
defaultSuccessUrl() | 设置登录成功后的默认跳转页面 |
failuerHandler() | 登录失败之后的处理器 |
successHandler() | 登录成功之后的处理器 |
failuerUrl() | 登录失败之后系统转向的url ,默认是this.loginPage + “?error” |
loginProcessingUrl() | 设置登录请求的 URL,即表单提交的 URL |
usernameParameter() | 设置登录表单中用户名字段的参数名,默认为 username |
passwordParameter() | 设置登录表单中密码字段的参数名,默认为 password |
登出logout
方法 | 说明 |
---|---|
logoutUrl() | 登出url , 默认是/logout l |
logoutSuccessUrl() | 登出成功后跳转的 url 默认是/login?logout |
logoutSuccessHandler() | 登出成功处理器,设置后会把logoutSuccessUrl 置为null |
自定义登录页
<h1>用户登录</h1>
<form action="/userLogin" method="post">
? ?<label>用户:</label><input type="text" name="username"/><br/>
? ?<label>密码:</label><input type="password" name="password"/><br/>
? ?<input type="submit" value="登录"/>
</form>
创建UserController
@Controller
public class UserController {
@RequestMapping("/toLogin")
public String toLogin() {
return "login";
}
@RequestMapping("/userLogin")
public String userLogin(String username, String password) {
System.out.println("username=" + username + ",password=" + password);
return "index";
}
@RequestMapping("/admin/toAddUser")
public String toAddUser() {
return "admin/addUser";
}
@RequestMapping("/admin/toListUser")
public String toListUser() {
return "admin/listUser";
}
@RequestMapping("/admin/toResetPwd")
public String toResetPwd() {
return "admin/resetPwd";
}
@RequestMapping("/admin/toUpdateUser")
public String toUpdateUser() {
return "admin/updateUser";
}
@RequestMapping("/user/toUpdatePwd")
public String toUpdatePwd() {
return "user/updatePwd";
}
@RequestMapping("/noAccess")
public String noAccess() {
return "accessDenied";
}
}
创建
WebSecurityConfig
配置类,设置@EnableWebSecurity
注解开启Spring Security
的默认行为。@Configuration //开启SpringSecurity的默认行为 @EnableWebSecurity public class WebSecurityConfig { ? ? ?@Bean ? ?public SecurityFilterChain securityFilterChain(HttpSecurity http) ? ? ? ? ? ?throws Exception{ ? ? ? ?http.authorizeRequests() ? ? ? ? ? ? ? ? ? ?// 开放接口访问权限,不需要登录就可以访问 ? ? ? ? ? ? ? ? ? .antMatchers("/toLogin").permitAll() ? ? ? ? ? ? ? ? ? ?// 其余所有请求全部需要鉴权认证 ? ? ? ? ? ? ? ? ? .anyRequest().authenticated() ? return http.build(); ? ? ? } }
配置完毕之后,请重启项目访问主页页面进行测试。
除了开放接口能正常访问以外,其他接口均提示403错误。
@Configuration
//开启SpringSecurity的默认行为
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception{
http.authorizeRequests()
// 开放接口访问权限,不需要登录授权就可以访问
.antMatchers("/toLogin").permitAll()
// 其余所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.formLogin()
// 设置登录页面的 URL
.loginPage("/toLogin")
// 设置登录请求的 URL,即表单提交的 URL
.loginProcessingUrl("/userLogin")
// 设置登录表单中用户名字段的参数名,默认为username
.usernameParameter("username")
// 设置登录表单中密码字段的参数名,默认为password
.passwordParameter("password")
;
return http.build();
}
}
配置完毕之后,请重启项目访问登录页
修改
SecurityConfig
配置类,加入Spring Security
安全退出设置。@Configuration //开启SpringSecurity的默认行为 @EnableWebSecurity public class WebSecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{ http.authorizeRequests() // 开放接口访问权限,不需要登录授权就可以访问 .antMatchers("/toLogin").permitAll() // 其余所有请求全部需要鉴权认证 .anyRequest().authenticated() .and() .formLogin() // 设置登录页面的 URL .loginPage("/toLogin") // 设置登录请求的 URL,即表单提交的 URL .loginProcessingUrl("/userLogin") // 设置登录表单中用户名字段的参数名,默认为username .usernameParameter("username") // 设置登录表单中密码字段的参数名,默认为password .passwordParameter("password") .and() .logout() // 设置安全退出的URL路径 .logoutUrl("/logout") // 设置退出成功后跳转的路径 .logoutSuccessUrl("/") ; return http.build(); } }
配置完毕之后,请重启项目。先跳转到登录页面,输入账号密码登录,然后再点击安全退出按钮测试是否成功退出。
首先请删除或注释掉application.yml
文件中的配置的自定义账号和密码。
spring:
freemarker:
? suffix: .ftl
? template-loader-path: classpath:/templates/
? enabled: true
# security:
# ? user:
# ? ? name: admin
# ? ? password: 123456
修改SecurityConfig
配置类,加入多用户角色配置。
@Bean
public UserDetailsService userDetailsService() {
UserDetails admin = User.withUsername("admin")
.password(bcryptPasswordEncoder().encode("123456"))
.roles("ADMIN", "USER").build();
UserDetails user = User.withUsername("user")
.password(bcryptPasswordEncoder().encode("123456"))
.roles("USER").build();
return new InMemoryUserDetailsManager(admin, user);
}
异常说明:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
解决方案:这个错误通常是因为在使用
Spring Security
进行密码验证时没有正确地配置密码编码器。在Spring Security
中,密码编码器用于将用户提供的密码编码为安全的散列值,并将其与存储在数据库中的散列值进行比较。如果没有正确地配置密码编码器,则Spring Security
将无法识别密码编码器的类型,并在比较密码时引发“没有映射到 id“null”的密码编码器”异常。要解决这个问题,需要在Spring Security
配置中配置一个密码编码器,并将其用于验证用户提供的密码。常用的密码编码器有BCryptPasswordEncoder
和StandardPasswordEncoder
。修改
SecurityConfig
配置类,配置密码编码器。@Bean public PasswordEncoder bcryptPasswordEncoder() { return new BCryptPasswordEncoder(); }
重启项目,重新使用账号和密码登录进行测试。
修改SecurityConfig
配置类,设置不同角色的访问权限。
@Configuration
//开启SpringSecurity的默认行为
@EnableWebSecurity
public class WebSecurityConfig {
?
? ?@Bean
? ?public SecurityFilterChain securityFilterChain(HttpSecurity http)
? ? ? ? ? ?throws Exception{
? ? ? ?http.authorizeRequests()
? ? ? ? ? ? ? ? ? ?// 开放接口访问权限,不需要登录授权就可以访问
? ? ? ? ? ? ? ? ? .antMatchers("/toLogin").permitAll()
? ? ? ? ? ? ? ? ? ?// 设置角色权限
? ? ? ? ? ? ? ? ? .antMatchers("/admin/**").hasRole("ADMIN")
? ? ? ? ? ? ? ? ? .antMatchers("/user/**").hasAnyRole("ADMIN","USER")
? ? ? ? ? ? ? ? ? ?// 其余所有请求全部需要鉴权认证
? ? ? ? ? ? ? ? ? .anyRequest().authenticated()
? ? ? ? ? ? ? .and()
? ? ? ? ? ? ? ? ? .formLogin()
? ? ? ? ? ? ? ? ? ?// 设置登录页面的 URL
? ? ? ? ? ? ? ? ? .loginPage("/toLogin")
? ? ? ? ? ? ? ? ? ?// 设置登录请求的 URL,即表单提交的 URL
? ? ? ? ? ? ? ? ? .loginProcessingUrl("/userLogin")
? ? ? ? ? ? ? ? ? ?// 设置登录表单中用户名字段的参数名,默认为username
? ? ? ? ? ? ? ? ? .usernameParameter("username")
? ? ? ? ? ? ? ? ? ?// 设置登录表单中密码字段的参数名,默认为password
? ? ? ? ? ? ? ? ? .passwordParameter("password")
? ? ? ? ? ? ? .and()
? ? ? ? ? ? ? ? ? .logout()
? ? ? ? ? ? ? ? ? ?// 设置安全退出的URL路径
? ? ? ? ? ? ? ? ? .logoutUrl("/logout")
? ? ? ? ? ? ? ? ? ?// 设置退出成功后跳转的路径
? ? ? ? ? ? ? ? ? .logoutSuccessUrl("/")
? ? ? ? ? ? ? ;
? ? ? ?return http.build();
? }
}
重启项目,跳转登录页重新登录。这时发现登录不成功,后台控制台也没有产生任何异常信息,通过浏览器的网络(network)查看现实登录请求接口302错误。
解决方案:关闭csrf
修改
SecurityConfig
配置类,添加关闭csrf
配置。
http.csrf().disable();
再次重启项目,发现登录正常。
最后通过切换不同的用户(user
和admin
)来测试角色权限是否授权成功,admin
账号访问所有接口权限正常;切换到user
账号访问时,发现以/admin/**
开头的接口不能访问(证明鉴权成功),但是却提示403错误。
解决方案:配置自定义异常处理器
修改
SecurityConfig
配置类,添加自定义异常处理,并设置异常处理页面。http.exceptionHandling().accessDeniedPage("/noauth")
重启项目后发现无权限将跳转到自定义异常页面。