加入如下依赖
org.springframework.boot
spring-boot-starter-security
创建一个测试Controller如下
启动项目后浏览器访问 http://localhost:8080/hello
提示登录
application.yml 配置 security的 name 与 password
登录后即可正常访问
管理员,同事具有 ADMIN,USER权限,可以访问所有资源
普通用户,只能访问 /product/**
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
// 管理员,同事具有 ADMIN,USER权限,可以访问所有资源
.withUser("admin1").password(new BCryptPasswordEncoder().encode("admin1")).roles("ADMIN","USER")
.and().passwordEncoder(new BCryptPasswordEncoder())
// 普通用户,只能访问 /product/**
.withUser("user1").password(new BCryptPasswordEncoder().encode("user1")).roles("USER");
}
管理员:ADMIN
用户:USER
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception{
httpSecurity.authorizeRequests().antMatchers("/product/**").hasAnyRole("USER")
.antMatchers("/admin/**").hasAnyRole("ADMIN")
.anyRequest().authenticated().and().formLogin().and().httpBasic();
}
通过SecurityContextHolder来获取用户信息
@RequestMapping("/info")
@ResponseBody
public String product(){
//控制器获取当前登录用户
String currentUser = "";
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if(principal instanceof UserDetails){
currentUser = ((UserDetails) principal).getUsername();
}else {
currentUser = principal.toString();
}
return "配太华以为刀兮、长四读以为佩. ----"+currentUser;
}
spring security核心组件有:
SecurityContext、SecurityContextHolder、Authentication、Userdetails 和 AuthenticationManager等
安全上下文,用户通过Spring Security 的校验之后,验证信息存储在SecurityContext中,SecurityContext的接口定义如下:
定义了两个方法,实际上其主要作用就是获取Authentication对象
是一个持有者,用来持有住SecurityContext实例的
在Spring Security中,在请求之间存储SecurityContext的责任落在SecurityContextPersistenceFilter上,默认情况下,该上下文将上下文存储为HTTP请求之间的HttpSession属性。它会为每个请求恢复上下文SecurityContextHolder,并且最重要的是,在请求完成时清除SecurityContextHolder。SecurityContextHolder是一个类,他的功能方法都是静态的(static)。
SecurityContextHolder可以设置指定JVM策略(SecurityContext的存储策略)
这个策略有三种:
MODE_THREADLOCAL:SecurityContext 存储在线程中。
MODE_INHERITABLETHREADLOCAL:SecurityContext 存储在线程中,但子线程可以获取到父线程中的 SecurityContext。
MODE_GLOBAL:SecurityContext 在所有线程中都相同。
SecurityContextHolder默认使用MODE_THREADLOCAL模式,即存储在当前线程中。
//其作用就是存储当前认证信息
SecurityContextHolder.getContext().setAuthentication(token);
一组用户名密码信息.
该接口有四个get方法,分别表示
/*
*获取用户权限,一般情况下获取到的是用户的角色信息。
*/
getAuthorities:
/*
*获取证明用户认证的信息,通常情况下获取到的是密码等信息。
*/
getCredentials:
/*
*获取用户的额外信息,(这部分信息可以是我们的用户表中的信息)。
*/
getDetails:
/*
*获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是
* UserDetails (UserDetails也是一个接口,里边的方法有getUsername,getPassword等)。
*/
getPrincipal:
/*
*获取当前 Authentication 是否已认证。
*/
isAuthenticated:
/*
*设置当前 Authentication 是否已认证(true or false)。
*/
setAuthenticated:
存储用户信息
方法含义如下
/*
*获取用户权限,本质上是用户的角色信息。
*/
getAuthorites
/*
*获取密码。
*/
getPassword
/*
*获取用户名。
*/
getUserName
/*
*账户是否过期。
*/
isAccountNonExpired
/*
*账户是否被锁定。
*/
isAccountNonLocked
/*
*密码是否过期。
*/
isCredentialsNonExpired
/*
*账户是否可用。
*/
isEnabled
UserDetailsService也是一个接口,且只有一个方法loadUserByUsername,他可以用来获取UserDetails。
通常会定义一个CustomUserDetailsService来实现UserDetailsService接口,并实现其loadUserByUsername方法。在实现loadUserByUsername方法的时候,可以通过查询数据库(或者是缓存、或者是其他的存储形式)来获取用户信息,然后组装成一个UserDetails,并返回。
AuthenticationManager 是一个接口,它只有一个方法,接收参数为Authentication
AuthenticationManager 的作用就是校验Authentication,
如果验证失败会抛出AuthenticationException异常。
Spring Security 在 Filter 中创建 Authentication 对象,并调用 AuthenticationManager 进行校验.
spring security 维护了一个filter chain
chain中的每一个filter都具有特定的责任,并根据所需的服务在配置总添加。
filter的顺序很重要,因为他们之间存在依赖关系。
pring security中有如下filter(按顺序的):
1.ChannelProcessingFilter:因为它可能需要重定向到不同的协议
2.SecurityContextPersistenceFilter:可以在web请求开头的SecurityContextHolder中设置SecurityContext:并且SecurityContext的任何更改都可以复制到HttpSession当web请求结束时准备好与下一个web请求一起使用
3.ConcurrentSessionFilter:身份验证处理-UsernamePasswordAuthenticationFilter,CasAuthenticationFilter,BasicAuthenticationFilter等。以便SecurityContextHolder可以修改为包含有效的Authentication请求令牌
4.SecurityContextHolderAwareRequestFilter:包装请求对象request
5.JaasApiIntegrationFilter
6.RememberMeAuthenticationFilter:记住我服务处理
7.AnonymousAuthenticationFilter:匿名身份处理,更新SecurityContextHolder
8.ExceptionTranslationFilter:获任何Spring Security异常,以便可以返回HTTP错误响应或启动适当的AuthenticationEntryPoint
9.FilterSecurityInterceptor:用于保护web URI并在访问被拒绝时引发异常
DelegatingFilterProxy是一个特殊的filter,存在于spring-web模块中。DelegatingFilterProxy通过继承GenericFilterBean使得自己变为了一个Filter(因为GenericFilterBean implements Filter)。它是一个Filter,其命名却以proxy结尾。
myFilter
org.springframework.web.filter.DelegatingFilterProxy
myFilter
/*
此配置是我们使用web.xml配置Filter时做法。但是与普通的Filter不同的是DelegatingFilterProxy并没有实际的过滤逻辑,他会尝试寻找filter-name节点所配置的myFilter,并将过滤行为委托给myFilter来处理。这种方式能够利用Spring丰富的依赖注入工具和生命周期接口,因此DelegatingFilterProxy提供了web.xml与应用程序上下文之间的链接。
spring security入口
在没有spring boot之前,我们要使用spring security的话,通常在web.xml中添加如下配置:
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
这里配置的是DelegatingFilterProxy。它实际上会去找到filter-name节点中的Filter——springSecurityFilterChain,并将实际的过滤工作交给springSecurityFilterChain处理。
在使用spring boot之后,这一xml配置被Java类配置给代替了。@EnableWebSecurity 注解,通过跟踪源码可以发现@EnableWebSecurity会加载WebSecurityConfiguration类,而WebSecurityConfiguration类中就有创建springSecurityFilterChain这个Filter的代码:
spring security 的核心是基于filter
入口filter是springSecurityFilterChain(它会被DelegatingFilterProxy委托来执行过滤任务)
springSecurityFilterChain实际上是FilterChainProxy (一个filter)
FilterChainProxy里边有一个SecurityFilterChain集合,doFIlter的时候会从其中取。
准备:整合mybatis连接本地数据库
/**
* ID
*/
private Integer id;
/**
* 账号
*/
private String name;
/**
* 密码
*/
private String password;
/**
* 性别
*/
private String sex;
查询用户角色以及设置角色、即上面的userFromDatabase.getRole()通常是一个list、所以设置角色的时候,就是for循环new 多个SimpleGrantedAuthority并设置。
@Component("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
public static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();
@Autowired
private Userservice userservice;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 查询用户
User user = userservice.getOne(username);
// boolean s = PASSWORD_ENCODER.matches("123156", "$2a$10$69twjsLqHXDIxObXBTqV6uUrkp0UgglnTjPbQKSZyQibTq4VRKgsG");
if (user==null){
throw new UsernameNotFoundException("User " + username + " was not found in db");
//这里找不到必须抛异常
}
// 2. 设置角色
Collection grantedAuthorities = new ArrayList<>();
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(user.getSex());
grantedAuthorities.add(grantedAuthority);
return new org.springframework.security.core.userdetails.User(username,user.getPassword(), grantedAuthorities);
}
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)// 设置自定义的userDetailsService
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
spring security 可以准确控制session何时创建以及Spring Security如何与之交互:
always 没有session就创建。
ifRequired (default),如果需要就创建(默认)。
never
stateless 不创建不使用session
需要在SecurityConfiguration中修改为stateless状态
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)