当我们创建一个新的web项目时,安全方案是必不可少的,主流的权限框架Spring Security、Apache shiro
Apache shiro 和 Spring Security 比较:
权限 | 说明 |
---|---|
Apache shiro | Java安全框架,可以不依赖Spring 在java 项目中使用 |
Spring Security | Spring 家族中的一个安全管理框架,非常容易和Spring整合 |
本例介绍的是Spring Security的SpringBoot 集成方式
form表单认证流程
通常通过 form 表单提交 Username/Password 验证
请求示例图:
这是一个安全过滤链 SecurityFilterChain
图解,
1、访问一个没有授权的请求
2、 Spring Security 的 FilterSecurityInterceptor
通过抛出一个 AccessDeniedException 的异常表明请求无效
3、 ExceptionTranslationFilter
捕获到异常,使用重定向到 AuthenticationEntryPoint
配置的登录页,表单认证通常使用 LoginUrlAuthenticationEntryPoint
,是一个 AuthenticationEntryPoint
的实例
4、浏览器将请求重定向到登录页
5、反馈一些信息到登录页面
提交用户名密码
当用户密码被提交时, UsernamePasswordAuthenticationFilter
将对用户和密码进行认证. UsernamePasswordAuthenticationFilter` 继承 AbstractAuthenticationProcessingFilter,
UsernamePasswordAuthenticationFilter 内部工作流程类似如下图
1、当用户提交用户名密码时, UsernamePasswordAuthenticationFilter
通过从HttpServletRequest
获取 username and password,创建一个Authentication
类型的 UsernamePasswordAuthenticationToken
2、接下来将 UsernamePasswordAuthenticationToken
传递给AuthenticationManager
进行认证,AuthenticationManager
如何去获取用户权限,依赖用户信息储存机制:
- 内存存储 In-Memory Authentication
- 关系型数据库储存 JDBC Authentication
- 自定义存储 UserDetailsService
- LDAP存储 LDAP Authentication
3、认证失败
- SecurityContextHolder 删除安全上下文.
RememberMeServices.loginFail
被调用AuthenticationFailureHandler
认证失败调用
4、认证成功
SessionAuthenticationStrategy
认证session处理- 将 Authentication 认证信息设置到当前线程 SecurityContextHolder.
RememberMeServices.loginSuccess
调用记住用户.ApplicationEventPublisher
分配成功发布一个事件InteractiveAuthenticationSuccessEvent
.- 成功之后处理
AuthenticationSuccessHandler
调用
Spring Security 的安全过滤链中的过滤器,每个过滤器都有各自的功能
FilterChainProxy #过滤链代理, 请求会先经过此处,然后依次经过过滤链中的过滤器
LogoutFilter
DefaultLogoutPageGeneratingFilter
UsernamePasswordAuthenticationFilter # 验证用户密码
FilterSecurityInterceptor # 决定用户是否有权限访问资源
代码开发:
springboot 使用安全框架非常简单,在 maven 的pom.xml 中引入下面2个依赖,基本就完成了方案引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
通过配置文件配置用户和角色
#配置默认权限用户
spring.security.user.name=username
#配置默认权限密码
spring.security.user.password=user
#spring.security.user.roles=ADMIN
#spring.security.user.roles=USER
#
spring.security.user.roles=ADMIN,USER
配置一个资源和角色的映射
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//admin 请求的需要ADMIN角色
//其他的 只要身份验证通过就可以
http.authorizeRequests().
mvcMatchers("/other/**").permitAll().
mvcMatchers("/admin/**").hasRole("ADMIN").
mvcMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
}
新建立一个测试的Controller
@RestController
public class IndexController {
/**
* 只要登录就可以访问
* @return
*/
@RequestMapping(value = {"/index" }, method = RequestMethod.GET)
public ResponseEntity index(){
return ResponseEntity.ok().data("Hello,index");
}
/**
* ADMIN 角色就可以访问
* @return
*/
@RequestMapping(value = {"/admin" }, method = RequestMethod.GET)
public ResponseEntity admin(){
return ResponseEntity.ok().data("Hello,admin");
}
/**
* USER 角色就可以访问
* @return
*/
@RequestMapping(value = {"/user" }, method = RequestMethod.GET)
public ResponseEntity user(){
return ResponseEntity.ok().data("Hello,user");
}
/**
* 无需权限就可以访问
* @return
*/
@RequestMapping(value = {"other" }, method = RequestMethod.GET)
public ResponseEntity other(){
return ResponseEntity.ok().data("Hello,other");
}
}
目前演示的是使用内存的方式进行用户名和角色的设置,后续关系可以放在数据库中
角色CODE | 资源 | 角色名字 |
---|---|---|
ADMIN | /admin/** | 超级管理员 |
USER | /user/** | 普通用户 |
然后就可以访问测试了,访问 http://127.0.0.1:9300/sync/index 会跳转到 http://127.0.0.1:9300/sync/login