Spring Security基础内容
文章目录
Spring Security基础内容
刚接触,试图理解
资源
创建
依赖导入:
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
指定版本:
不指定就用默认默认设置的版本
<properties>
<!-- ... -->
<spring-security.version>5.3.4.RELEASE</spring-security.version>
</dependencies>
特征:
- Authentication:鉴定,验证正在登录者的身份。
- Authorization:授权
- PasswordEncoder接口,像是存储密码的地方
SpringBoot自动做了三件事:
- 启用默认配置,创造了个过滤器
bean
,命名为springSecurityFilterChain
,这个bean对所有的安全负责。如URLs、验证用户名和密码、重定向等。 - 创建了一个
UserDetailsServcie
bean,用户名是user
,密码是随机的可以在console
的log
看到。 - 注册了一个过滤器
bean
,命名为springSecurityFilterChain
,是每个request
的Servlet
容器。
结构:
现在要做验证和鉴权的第一步:
创建自定义用户:
使用AuthenticationManagerBuilder
的inMemoryAuthentication()
方法在内存中创建,可设置用户名、密码、指定权限,注意密码需要加密。
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER","ADMIN");
完整代码:configure(HttpSecurity)
部分可以看后面的解析。
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// 利用参数不同做重载,auth是创建用户
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/**
* 在内存中创建一个名为 "user" 的用户,密码为 "pwd",拥有 "USER" 权限,密码使用BCryptPasswordEncoder加密
*/
// 看了文档还是有点用的,passwordEncoder是密码库,有不同的加密方式
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("user").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER");
/**
* 在内存中创建一个名为 "admin" 的用户,密码为 "pwd",拥有 "USER" 和"ADMIN"权限
*/
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER","ADMIN");
}
}
验证
自定义验证方法
-
首先,要能接收到前端输入的用户名和密码,并存储起来。
一个重要的实例:
将前端获取的用户的凭证(也就是密码)、细节、职务等信息保存起来,第一个方法就是获取用户的权限信息集合。public interface Authentication extends Principal, Serializable { Collection<? extends GrantedAuthority> getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); boolean isAuthenticated(); void setAuthenticated(boolean var1) throws IllegalArgumentException; }
-
有了用户信息之后,就可以进行验证了,这里自定义一个验证方法。
自定义验证类:
功能是只要用户名是alex
,就能通过验证,并加上ADMIN
和USER
权限。/** * Created by wxb on 2018/10/21 0021. * 自定义验证类,可以用作backdoor,例如输入:alex/任意密码就可以通过验证 */ @Component public class BackdoorAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 这个方法是Principal的 String name = authentication.getName(); String password = authentication.getCredentials().toString(); //利用alex用户名登录,不管密码是什么都可以,伪装成admin用户 if (name.equals("alex")) { // 自己造了个,按理说应是getAuthorities()方法? Collection<GrantedAuthority> authorityCollection = new ArrayList<>(); authorityCollection.add(new SimpleGrantedAuthority("ROLE_ADMIN")); authorityCollection.add(new SimpleGrantedAuthority("ROLE_USER")); // 设置职务、密码、权限 // 有几个方法确实是继承了Authentication return new UsernamePasswordAuthenticationToken( "admin", password, authorityCollection); } else { return null; } } @Override public boolean supports(Class<?> authentication) { return authentication.equals( UsernamePasswordAuthenticationToken.class); } }
-
有了用户和自定义的验证方法,就可以走主验证程序了,要把自己定义的验证方法加进去。
主验证程序:
通过重写参数为AuthenticationManagerBuilder
类型的configure
,可以创建用户,同时添加自己设置的验证类,上面用了@Component
注释,所以可以自动装箱,加入到验证链ProviderManagerBuilder
中(AuthenticationManagerBuilder
实现了ProviderManagerBuilder
)。@EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired BackdoorAuthenticationProvider backdoorAuthenticationProvider; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /** * 在内存中创建一个名为 "user" 的用户,密码为 "pwd",拥有 "USER" 权限,密码使用BCryptPasswordEncoder加密 */ auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("user").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER"); /** * 在内存中创建一个名为 "admin" 的用户,密码为 "pwd",拥有 "USER" 和"ADMIN"权限 */ auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("admin").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER","ADMIN"); //将自定义验证类注册进去 auth.authenticationProvider(backdoorAuthenticationProvider); } }
-
验证完了以后,验证信息会存在
SecurityContextHolder
中,可以直接其中的Authentication
对象。
获取验证信息:controller
层:/** * 定义用户相关网址映射的Controller */ @Controller public class UserController { @RequestMapping("/user") public String user(@AuthenticationPrincipal Principal principal, Model model){ model.addAttribute("username", principal.getName()); //从SecurityContextHolder中得到Authentication对象,进而获取权限列表,传到前端 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); Collection<GrantedAuthority> authorityCollection = (Collection<GrantedAuthority>) auth.getAuthorities(); model.addAttribute("authorities", authorityCollection.toString()); return "user/user"; } @RequestMapping("/admin") public String admin(@AuthenticationPrincipal Principal principal, Model model){ model.addAttribute("username", principal.getName()); //从SecurityContextHolder中得到Authentication对象,进而获取权限列表,传到前端 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); Collection<GrantedAuthority> authorityCollection = (Collection<GrantedAuthority>) auth.getAuthorities(); model.addAttribute("authorities", authorityCollection.toString()); return "admin/admin"; } }
流程顺一遍:
先是index
页面提交,然后跳转/login
,请求被SecurityConfiguration
类的confiure(HttpSecurity)
方法捕获,之后会进行验证,走configure(AuthenticationManagerBuilder auth)
,这里可以不通过数据库添加用户,还可以添加自定义的验证类BackdoorAuthenticationProvider
,然后就会用接收的username
和password
包装成的Authentication
类对所有的验证类进行验证,只要有一个能通过就行。
使用数据库进行验证
两个关键接口:
-
Security
提供了两个核心接口UserDetails
和UserDetailsService
。 -
UserDetails
定义了验证信息的详细方法。 -
UserDetailsService
的实现类中的方法loadUserByUsername
通过用户名读取用户信息(这里需要用到操作数据的Mapper
),返回一个UserDetails
的实现类 - 之后,这个
UserDetailsService
就要作为验证链的一员了,添加方法和前面的也不一样://加入数据库验证类,下面的语句实际上在验证链中加入了一个DaoAuthenticationProvider auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
UserDetails的实现类:
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Created by wxb on 2018/10/23 0023.
* 用户表user对应的类,同时实现了UserDetails接口,成为登录验证的信息类
*/
@Component
public class MyUserBean implements UserDetails {
private Long id;
private String name;
private String address;
private String username;
private String password;
private String roles;
/**
* 从数据库中取出roles字符串后,进行分解,构成一个GrantedAuthority的List返回
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 对数据库中的内容进行解析,返回一个权限类的数组
String[] authorities = roles.split(",");
List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
for (String role : authorities) {
simpleGrantedAuthorities.add(new SimpleGrantedAuthority(role));
}
return simpleGrantedAuthorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
UserDetailsService的实现类:
import com.apkkids.bean.MyUserBean;
import com.apkkids.mapper.MyUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* Created by wxb on 2018/10/23 0023.
* UserDetailsService的实现类,用于在程序中引入一个自定义的AuthenticationProvider,实现数据库访问模式的验证
*
*/
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
MyUserMapper mapper;
// 接口自带方法,返回一个UserDetails的实现类
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
MyUserBean userBean = mapper.selectByUsername(username);
if (userBean == null) {
throw new UsernameNotFoundException("数据库中无此用户!");
}
return userBean;
}
}
添加到验证链中:
最下面那句就是关键,又加了一种验证方法,可以查询数据库内容,其他地方都差一样。
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
BackdoorAuthenticationProvider backdoorAuthenticationProvider;
@Autowired
MyUserDetailsService myUserDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/**
* 在内存中创建一个名为 "user" 的用户,密码为 "pwd",拥有 "USER" 权限,密码使用BCryptPasswordEncoder加密
*/
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("user").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER");
/**
* 在内存中创建一个名为 "admin" 的用户,密码为 "pwd",拥有 "USER" 和"ADMIN"权限
*/
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER","ADMIN");
//将自定义验证类注册进去
auth.authenticationProvider(backdoorAuthenticationProvider);
//加入数据库验证类,下面的语句实际上在验证链中加入了一个DaoAuthenticationProvider
auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
}
鉴权
-
首先要有权限,如何获得知道网页对应的权限?实现
FilterInvocationSecurityMetadataSource
类:
MySecurityMetadataSource:
从前端获取url,根据url来判断属于那种权限地址。@Component public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Autowired ResourceMapper resourceMapper; //本方法返回资访问源所需的角色集合 AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { // 从object中得到需要访问的资源,即网址 // 这个应是网页输入、跳转 String requestUrl = ((FilterInvocation) object).getRequestUrl(); // 从数据库中得到所有资源,以及对应的角色 List<MyResourceBean> resourceBeans = resourceMapper.selectAllResource(); for (MyResourceBean resource : resourceBeans) { // 首先进行地址匹配 // url对上了,且有对应角色; 看这样子URL是不能重复的 if (antPathMatcher.match(resource.getUrl(), requestUrl) && resource.getRolesArray().length > 0) { return SecurityConfig.createList(resource.getRolesArray()); } } //匹配不成功返回一个特殊的ROLE_NONE return SecurityConfig.createList("ROLE_NONE"); } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } }
-
知道了网站需要的权限,就要看该用户有没有这种权限,用户的权限可以通过实现了
UserDetails
的MyUserBean
获得,然后将用户的权限和网站跳转需要的权限匹配,具体是实现AccessDecisionManager
接口。
MyAccessDecisionManager:@Component public class MyAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { // 从authentication中获取当前用户具有的权限,一个角色可能有多种权限 Collection<? extends GrantedAuthority> userAuthorities = authentication.getAuthorities(); // 从configAttributes中获取访问资源所需要的权限,它来自MySecurityMetadataSource的getAttributes // 有的资源多个权限都可以访问 Iterator<ConfigAttribute> iterator = configAttributes.iterator(); // 所以这里就是:用户的多个权限——匹配——可查看该资源的多个权限 while (iterator.hasNext()) { // 这属性是权限吧,为啥是两步? // 答:类就是这么设计的 ConfigAttribute attribute = iterator.next(); // 获取角色信息了 String role = attribute.getAttribute(); // 匹配不上 if ("ROLE_NONE".equals(role)) { if (authentication instanceof AnonymousAuthenticationToken) { throw new BadCredentialsException("用户未登录"); } else return; } // 逐一对角色拥有的权限进行匹配 for (GrantedAuthority authority : userAuthorities) { if (authority.getAuthority().equals("ROLE_ADMIN")) { return; //用户具有ROLE_ADMIN权限,则可以访问所有资源 } if (authority.getAuthority().equals(role)) { return; //匹配成功就直接返回 } } } //不能完成匹配 throw new AccessDeniedException("你没有访问" + object + "的权限!"); } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class<?> clazz) { return true; } }
-
如果匹配不上的话,会抛出一个
AccessDeniedException
异常,这个异常被AccessDeniedHandler
接口的实现类MyAccessDeniedHandler
接收,可以生成一个json
对象。
MyAccessDeniedHandler:// 处理没通过的请求 @Component public class MyAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setContentType("application/json;charset=UTF-8"); PrintWriter out = response.getWriter(); ResponseBean info = new ResponseBean(500, accessDeniedException.getMessage(), null); out.write(new ObjectMapper().writeValueAsString(info)); out.flush(); out.close(); } }
-
方法都写完了,剩下的就是绑定了,即在
WebSecurityConfigurerAdapter
接口的实现类SecurityConfiguration
中配置MyAccessDecisionManager
,MySecurityMetadataSource
,MyAccessDeniedHandler
。使用.withObjectPostProcessor
,这个方法名字也挺好记的,对象传递处理器
SecurityConfiguration:@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // 注意这个with方法,绑定网页权限分析和匹配决策方法 .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O object) { object.setSecurityMetadataSource(mySecurityMetadataSource); object.setAccessDecisionManager(myAccessDecisionManager); return object; } }) .and() .formLogin().loginPage("/login_p").loginProcessingUrl("/login").permitAll() //1.自定义参数名称,与login.html中的参数对应 .usernameParameter("myusername").passwordParameter("mypassword") .and() .logout().logoutUrl("/logout").logoutSuccessUrl("/login").permitAll() // 后面这些是处理匹配不上的情况 .and() .csrf().disable() .exceptionHandling().accessDeniedHandler(myAccessDeniedHandler); } }
VBlog项目实例
项目地址
项目代码理解
重点就在configure(HttpSecurity http)
的分析中。
代码:
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 简简单单,只用了数据库
auth.userDetailsService(userService);
}
// 登录的时候走了这里
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// authenticated是已认证的意思
.antMatchers("/admin/category/all").authenticated()
.antMatchers("/admin/**","/reg").hasRole("超级管理员")///admin/**的URL都需要有超级管理员角色,如果使用.hasAuthority()方法来配置,需要在参数中加上ROLE_,如下.hasAuthority("ROLE_超级管理员")
// 任意URL,之所以可以放任意,是因为后面有找不到的操作方法
.anyRequest().authenticated()//其他的路径都是登录后即可访问
// 这个成功加了新东西
// formLogin()方法之后,链式就不再是HttpSecurity了,而是FormLoginConfigurer<H>
// successHandler指定成功的时候做的事情,并不影响跳转,就像是插进去的一样
.and().formLogin().loginPage("/login_page").successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("{\"status\":\"success\",\"msg\":\"登录成功\"}");
out.flush();
out.close();
}
})
// 指定失败的时候该做的事情
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("{\"status\":\"error\",\"msg\":\"登录失败\"}");
out.flush();
out.close();
}
}).loginProcessingUrl("/login")
.usernameParameter("username").passwordParameter("password").permitAll()
// 失败方法和知乎是一样的
.and().logout().permitAll().and().csrf().disable().exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/blogimg/**","/index.html","/static/**");
}
// 指定,自动装箱写后面了
@Bean
AccessDeniedHandler getAccessDeniedHandler() {
return new AuthenticationAccessDeniedHandler();
}
}
URL匹配与保护
应是总开关:authorizeRequests
:作用是允许限制访问基于HttpServletRequest使用RequestMatcher实现(即通过URL模式)。这意思就是开启路径限制?返回类型还挺独特的ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
匹配:requestMatchers()
:配置一个request Mather数组,参数为RequestMatcher 对象,其match 规则自定义,需要的时候放在最前面,对需要匹配的的规则进行自定义与过滤
antMatchers()
:配置一个request Mather 的 string数组,参数为 ant 路径格式, 直接匹配url
anyRequest()
:匹配任意url,无参 ,最好放在最后面
保护/限制:
我觉得叫限制更合适authenticated()
:保护UrL,需要用户登录
hasRole(String role)
:限制单个角色访问,角色将被增加 “ROLE_” .所以”ADMIN” 将和 “ROLE_ADMIN”进行比较. 另一个方法是hasAuthority(String authority)
hasAnyRole(String… roles)
:允许多个角色访问. 另一个方法是hasAnyAuthority(String… authorities)
configure(AuthenticationManagerBuilder auth)分析
这个就是配置验证链,比较简单:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 简简单单,只用了数据库
auth.userDetailsService(userService);
}
configure(HttpSecurity http)分析
就按着下面的链式调用方法展开:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// authenticated是已认证的意思
.antMatchers("/admin/category/all").authenticated()
.antMatchers("/admin/**","/reg").hasRole("超级管理员")///admin/**的URL都需要有超级管理员角色,如果使用.hasAuthority()方法来配置,需要在参数中加上ROLE_,如下.hasAuthority("ROLE_超级管理员")
// 任意URL,之所以可以放任意,是因为后面有找不到的操作方法
.anyRequest().authenticated()//其他的路径都是登录后即可访问
// 这个成功加了新东西
// formLogin()方法之后,链式就不再是HttpSecurity了,而是FormLoginConfigurer<H>
// successHandler指定成功的时候做的事情,并不影响跳转,就像是插进去的一样
.and().formLogin().loginPage("/login_page").successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("{\"status\":\"success\",\"msg\":\"登录成功\"}");
out.flush();
out.close();
}
})
// 指定失败的时候该做的事情
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("{\"status\":\"error\",\"msg\":\"登录失败\"}");
out.flush();
out.close();
}
}).loginProcessingUrl("/login")
.usernameParameter("username").passwordParameter("password").permitAll()
.and().logout().permitAll().and().csrf().disable().exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());
}
formLogin()
:调用了formLogin()
后,没参数的话就变类型了。作用是指定支持基于表单的身份验证(是不是从网页接收信息来的就是基于表单?),如果没指定后面的loginPage()
的话,就会自动生成一个(自动生成应该就是默认/login
吧)
- 无参情况返回
FormLoginConfigurer<HttpSecurity>
类型,即用这个类型进行配置。 - 有参情况返回
HttpSecurity
,即已经用参数配置完了, 再跳回去 - 如果没指定位置,那么默认跳到
/login
,失败就重定向到'/login?error
loginPage()
:方法是属于FormLoginConfigurer<H>
的,而loginPage()
指定登陆需要的位置,如果使用的WebSecurityConfigurerAdapter
,那么会默认指定一个(一般都用了吧)。
successHandler
:作用是当验证成功的时候运行这一块,是继承(AbstractAuthenticationFilterConfigurer
)的方法,参数是接口AuthenticationSuccessHandler
的onAuthenticationSuccess
方法,用的是没有过滤链的重载方法。onAuthenticationSuccess
当通过验证的时候才能被调用。
failureHandler
:跟上面对应,验证失败的时候调用。
loginProcessingUrl
:也是继承的,作用是指定验证凭证(credentials)的URL,参数是用来验证用户和密码的URL地址,然后通过usernameParameter
和passwordParameter
来指定来自HTTP的用来验证的参数名。
permitAll()
:无参数,相当于permitAll(true)
,true表示授予对url的访问权,false表示跳过此步骤,用来确保所有的用户都能访问某些页面;指定URL无需保护,一般应用与静态资源文件
感觉像个总开关?
and()
:继承自SecurityConfigurerAdapter
,功能是如果使用了SecurityConfigurer
就返回SecurityBuilder
(肯定用了吧,没用写他干嘛),返回类型是SecurityBuilder
(官方文档说有利于方法链接,确实是,这样就能从一个新的起点开始了)。
但是SecurityBuilder
里面啥方法也没有啊??
感觉这是一套终极继承关系来的!
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>> implements SecurityConfigurer<O, B> {
public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>> extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {
public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<LogoutConfigurer<H>, H> {
public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity> implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
最终在HttpSecurity
里找到了!
public LogoutConfigurer<HttpSecurity> logout() throws Exception {
return (LogoutConfigurer)this.getOrApply(new LogoutConfigurer());
}
所以,这个方法返回的并不是SecurityBuilder
,而是他的子类!也就是最开始的HttpSecurity
!破案成功。
logout()
:退出的时候用的,指定URL地址,默认是/logout
,功能是让HTTP的Session无效,同时清除rememberMe()
、SecurityContextHolder
(这个里面也有用户名和密码,用getContext方法可以得到),并且重定向到/login?success
。
csrf()
:开启CSRF支持,如果用了WebSecurityConfigurerAdapter
的话会默认开启,然后后面加个.disable()
就可以取消开启。disable()
返回的类型是HttpSecurityBuilder
,而csrf()
返回的类型是CsrfConfigurer
,所以不使用的话就直接返回最初了,使用的话就紧接着配置csrf
。
返回HttpSecurityBuilder
:
这些返回最初的方法都是用的this.getBuilder()
在and()
中:
public B and() {
return this.getBuilder();
}
在disable()
中
public B disable() {
((HttpSecurityBuilder)this.getBuilder()).removeConfigurer(this.getClass());
return (HttpSecurityBuilder)this.getBuilder();
}
所以作为整个类,WebSecurityConfig extends WebSecurityConfigurerAdapter
,这个类的getBuilder
就已经指定为configure(HttpSecurity http)
中的那个参数了。
exceptionHandling
:是添加异常处理的方法,然后返回一个异常处理配置器ExceptionHandlingConfigurer
,在调用其中的accessDeniedHandler(AccessDeniedHandler accessDeniedHandler)
方法来指定异常处理器,就是自己定义的那个,这个方法返回的还是ExceptionHandlingConfigurer
,不过到这里整个方法也结束了,如果想继续加东西,也可以再用and()
跳回去。
configure(WebSecurity web)分析
配置一些忽略内容:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/blogimg/**","/index.html","/static/**");
}
ignoring()
:作用是允许添加Spring Security应该忽略的RequestMatcher
实例,而RequestMatcher
是匹配HttpServletRequest
的简单策略。返回的类型是WebSecurity.IgnoredRequestConfigurer
,到这里也比较清晰了,就是对忽略的内容进行配置。配置的内容都不会被HttpServletRequest
获取,一般来说要忽略的都是静态资源,还有一些动态requests,将这些request映射为允许所有用户请求的样子。
推荐阅读
-
Spring Security认证提供程序示例详解
-
使用Spring Security控制会话的方法
-
spring-boot-klock-starter V1.1 主体功能重大更新内容介绍
-
浅谈Spring Security LDAP简介
-
如何使用Spring Security手动验证用户的方法示例
-
Spring Security OAuth2集成短信验证码登录以及第三方登录
-
Spring Boot Security OAuth2 实现支持JWT令牌的授权服务器
-
Spring Boot Security配置教程
-
Spring Security在标准登录表单中添加一个额外的字段
-
spring security 5.x实现兼容多种密码的加密方式