Spring Security4 学习笔记——01
Spring Security4 学习笔记
Spring Security是什么?
- Spring Security提供了基于Java EE的企业应用软件全面的安全服务。
- 它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能。
基础说明
- 1.注解 @EnableWebSecurity
- 该注解和 @Configuration 注解一起使用, 注解 WebSecurityConfigurer 类型的类,或者利用@EnableWebSecurity 注解继承 WebSecurityConfigurerAdapter的类,这样就构成了 Spring Security 的配置。
- @EnableWebSecurity 注解将会启用Web安全功能。
- 2.抽象类 WebSecurityConfigurerAdapter
- WebSecurityConfigurerAdapter 提供了一种便利的方式去创建 WebSecurityConfigurer的实例,只需要重写 WebSecurityConfigurerAdapter 的方法,即可配置拦截什么URL、设置什么权限等安全控制。
- 创建类SecurityConfiguration继承WebSecurityConfigurerAdapter,来对我们应用中所有的安全相关的事项(所有url,验证用户名密码,表单重定向等)进行控制。
configure(WebSecurity) 通过重载,配置Spring Security的Filter链
configure(HttpSecurity) 通过重载,配置如何通过拦截器保护请求
configure(AuthenticationManagerBuilder) 通过重载,配置user-detail服务
- 3.方法 configure(AuthenticationManagerBuilder auth) 和 configure(HttpSecurity http)
- 4.类 AuthenticationManagerBuilder
- AuthenticationManagerBuilder 用于创建一个 AuthenticationManager,让我能够轻松的实现内存验证、LADP验证、基于JDBC的验证、添加UserDetailsService、添加AuthenticationProvider。
基于内存的认证In-Memory Authentication
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("password").roles("USER").build());
manager.createUser(User.withUsername("admin").password("password").roles("USER","ADMIN").build());
return manager;
}
基于数据库表用户存储认证
- 通常我们都会将用户数据存储在关系型数据库中,并通过jdbc进行访问。spring security使用以jdbc为支撑的用户存储,我们可以使用下面的方式进行配置。
-
/**- 配置user-detail服务
- @param auth
- @throws Exception
*/
@Override
public void configure(AuthenticationManagerBuilder auth)throws Exception{
//基于数据库的用户存储、认证
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(“select account,password,true from user where account=?”)
.authoritiesByUsernameQuery(“select account,role from user where account=?”);
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
配置自定义的用户存储认证
- 这种方式更为灵活,更适合在生产环境使用。这种方式不在局限于存储环境。自定义的方式也很简单。只需要提供一个UserDetailService接口实现即可。
-
@Service
public class SecurityUserDetailsService implements UserDetailsService{@Autowired
private UserDao userDao;/**
- 校验用户
- @param username
- @return
-
@throws UsernameNotFoundException
*/
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userDao.queryByUserName(username);
if(user == null)throw new UsernameNotFoundException(“user not found”);return new org.springframework.security.core.userdetails.User(user.getAccount(),user.getPassword(),userDao.getUserGrantedAuthoritys(user.getId()));
}
}
密码加密策略
-
通常我们在存储密码的时候都是进行加密的,spring security默认提供了三种密码存储方式,同时也可以使用自定义的加密方式:
- NoOpPasswordEncoder 明文方式保存
- BCtPasswordEncoder 强hash方式加密
- StandardPasswordEncoder SHA-256方式加密
- 实现PasswordEncoder接口 自定义加密方式
-
/**- 配置user-detail服务
- @param auth
- @throws Exception
*/
@Override
public void configure(AuthenticationManagerBuilder auth)throws Exception{
//基于数据库的用户存储、认证
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(“select account,password,true from user where account=?”)
.authoritiesByUsernameQuery(“select account,role from user where account=?”)
.passwordEncoder(NoOpPasswordEncoder.getInstance());
}
请求拦截策略
- spring security的请求拦截匹配有两种风格,ant风格和正则表达式风格。编码方式是通过重载configure(HttpSecurity)方法实现。
-
/**- 拦截请求
- @param http
- @throws Exception
*/
@Override
public void configure(HttpSecurity http)throws Exception{
http.authorizeRequests()
.antMatchers(“/”,”/css/“,”/js/“).permitAll() //任何人都可以访问
.antMatchers(“/admin/**”).access(“hasRole(‘ADMIN’)”) //持有user权限的用户可以访问
.antMatchers(“/user/**”).hasAuthority(“ROLE_USER”);
}
access(String) 如果给定的SpEL表达式计算结果为true,就允许访问
anonymous() 允许匿名用户访问
authenticated() 允许认证过的用户访问
denyAll() 无条件拒绝所有访问
fullyAuthenticated() 如果用户是完整认证的话(不是通过Remember-me功能认证的),就允许访问
hasAnyAuthority(String...) 如果用户具备给定权限中的某一个的话,就允许访问
hasAnyRole(String...) 如果用户具备给定角色中的某一个的话,就允许访问
hasAuthority(String) 如果用户具备给定权限的话,就允许访问
hasIpAddress(String) 如果请求来自给定IP地址的话,就允许访问
hasRole(String) 如果用户具备给定角色的话,就允许访问
not() 对其他访问方法的结果求反
permitAll() 无条件允许访问
rememberMe() 如果用户是通过Remember-me功能认证的,就允许访问
/**
- 拦截请求
- @param http
-
@throws Exception
*/
@Override
public void configure(HttpSecurity http)throws Exception{
http.authorizeRequests()
.antMatchers(“/”,”/css/“,”/js/“).permitAll() //任何人都可以访问
.antMatchers(“/admin/**”).access(“hasRole(‘ADMIN’)”) //持有user权限的用户可以访问
.antMatchers(“/user/**”).hasAuthority(“ROLE_USER”)
.and()
.requiresChannel().antMatchers(“/admin/info”).requiresSecure();
}只要是对“/admin/info”的请求,spring security都认为需要安全性通道,并自动将请求重定向到https上。
与之相反,如果有些请求不需要https传送,可以使用requiresInsecure()替代requiresSecure(),将请求声明为始终使用http传送。
- spring security从版本3.2开始,默认就会启用CSRF防护。spring security通过一个同步token的方式来实现CSRF防护功能。它会拦截状态变化的请求,并检查CSRF token。如果请求中不包含CSRF token的话,或者token不能与服务器端的token匹配,请求就会失败,并抛出CsrfException异常。
- 这样就spring security就会自动生成csrf token。如果想关闭csrf防护,需要作的也很简单,只需要调用一下csrf().disable();即可。
-
/**- 拦截请求
- @param http
- @throws Exception
*/
@Override
public void configure(HttpSecurity http)throws Exception{
http.authorizeRequests()
.antMatchers(“/”,”/css/“,”/js/“).permitAll() //任何人都可以访问
.antMatchers(“/admin/**”).access(“hasRole(‘ADMIN’)”) //持有user权限的用户可以访问
.antMatchers(“/user/**”).hasAuthority(“ROLE_USER”)
.and().csrf().disable();
}
- remember-me是一个很重要的功能,用户肯定不希望每次都输入用户名密码进行登录。spring security提供的remember-me功能使用起来非常简单。启用这个功能只需要调用rememberMe()方法即可。
-
/**- 拦截请求
- @param http
- @throws Exception
*/
@Override
public void configure(HttpSecurity http)throws Exception{
http.authorizeRequests()
.antMatchers(“/”,”/css/“,”/js/“).permitAll() //任何人都可以访问
.antMatchers(“/admin/**”).access(“hasRole(‘ADMIN’)”) //持有user权限的用户可以访问
.antMatchers(“/user/**”).hasAuthority(“ROLE_USER”)
.and().rememberMe().key(“abc”).rememberMeParameter(“remember_me”).rememberMeCookieName(“my-remember-me”).tokenValiditySeconds(86400);
}
自定义登录页面
- spring security会提供一个默认的登录页面,如果你想使用自己的登录页面,可以这样设置。
-
/**- 拦截请求
- @param http
- @throws Exception
*/
@Override
public void configure(HttpSecurity http)throws Exception{
http.authorizeRequests()
.antMatchers(“/”,”/css/“,”/js/“).permitAll() //任何人都可以访问
.antMatchers(“/admin/**”).access(“hasRole(‘ADMIN’)”) //持有user权限的用户可以访问
.antMatchers(“/user/**”).hasAuthority(“ROLE_USER”)
.and().formLogin()
.loginPage(“/login”).usernameParameter(“username”).passwordParameter(“password”)
.and().exceptionHandling().accessDeniedPage(“/loginfail”);
}
-
- 通过formLogin()方法来设置使用自定义登录页面,loginPage是登录页面地址,accessDeniePage登录失败跳转地址。
handling Logouts
- 与配置登录功能类似,您也有各种选项来进一步定制您的注销需求:
- 注销后:
- 使HTTP会话无效
- 清理已配置的任何RememberMe身份验证
- 清除 SecurityContextHolder
- 重定向到 /login?logout
protected void configure(HttpSecurity http) throws Exception {
http
.logout()
.logoutUrl("/my/logout")
.logoutSuccessUrl("/my/index")
.logoutSuccessHandler(logoutSuccessHandler)
.invalidateHttpSession(true)
.addLogoutHandler(logoutHandler)
.deleteCookies(cookieNamesToClear)
.and()
...
}- 注意:
- 提供注销支持。使用时会自动应用WebSecurityConfigurerAdapter。
- 触发注销的URL(默认为/logout)。如果启用了CSRF保护(默认),则该请求也必须是POST。
- 注销后重定向到的URL。默认是/login?logout。
- 我们指定一个自定义LogoutSuccessHandler。如果指定了,logoutSuccessUrl()则忽略。
- 指定HttpSession在注销时是否使其无效。默认情况下这是真的。
- 添加一个LogoutHandler。默认情况下SecurityContextLogoutHandler添加为最后一个LogoutHandler。
- 允许指定在注销成功时删除的cookie的名称。
- LogoutHandler实现指示能够参与注销处理的类
- LogoutSuccessHandler 被成功注销后调用LogoutFilter,来处理如重定向或转发到相应的目的地。
- 提供以下实现:
- SimpleUrlLogoutSuccessHandler
- 无需SimpleUrlLogoutSuccessHandler直接指定。相反,流畅的API通过设置提供快捷方式logoutSuccessUrl()。这将设置SimpleUrlLogoutSuccessHandler封底。发生注销后,提供的URL将重定向到。默认是/login?logout。
- HttpStatusReturningLogoutSuccessHandler
- HttpStatusReturningLogoutSuccessHandler可以在REST API类型场景有趣。成功注销后,LogoutSuccessHandler 您可以提供要返回的纯HTTP状态代码,而不是重定向到URL 。如果未配置,则默认返回状态代码200。
- SimpleUrlLogoutSuccessHandler
- 提供以下实现:
AuthenticationProvider认证提供者
- 可以通过将自定义公开AuthenticationProvider为bean 来定义自定义身份验证。
@Bean
public SpringAuthenticationProvider springAuthenticationProvider() {
return new SpringAuthenticationProvider();
}
UserDetailsService用户详情服务
- 可以通过将自定义公开UserDetailsService为bean 来定义自定义身份验证。
- 例如,以下将自定义身份验证,假设SpringDataUserDetailsService实现UserDetailsService:
@Bean
public SpringDataUserDetailsService springDataUserDetailsService() {
return new SpringDataUserDetailsService();
}- 您还可以通过将PasswordEncoderbean 公开为bean 来自定义密码的编码方式。例如,如果使用bcrypt,则可以添加bean定义,如下所示:
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Multiple HttpSecurity 多重HttpSecurity
- 我们可以配置多个HttpSecurity实例,就像我们可以有多个块一样。关键是要WebSecurityConfigurerAdapter多次扩展。例如,以下是对以URL开头的不同配置的示例/api/。
-
@EnableWebSecurity public class MultiHttpSecurityConfig { @Bean public UserDetailsService userDetailsService() throws Exception { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("user").password("password").roles("USER").build()); manager.createUser(User.withUsername("admin").password("password").roles("USER","ADMIN").build()); return manager; } @Configuration @Order(1) // 1 public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) throws Exception { http .antMatcher("/api/**") // 2 .authorizeRequests() .anyRequest().hasRole("ADMIN") .and() .httpBasic(); } } @Configuration // 3 public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
} }@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin(); }
- 1.创建一个WebSecurityConfigurerAdapter包含的实例,@Order以指定WebSecurityConfigurerAdapter应首先考虑哪个实例。
- 这些http.antMatcher状态HttpSecurity仅适用于以。
- 3.创建另一个实例WebSecurityConfigurerAdapter。如果URL未/api/以此配置启动,则将使用此配置。之后考虑此配置,ApiWebSecurityConfigurationAdapter因为它具有之后的@Order值1(无@Order默认为最后)。
方法安全
- EnableGlobalMethodSecurity
- 可以@EnableGlobalMethodSecurity在任何@Configuration实例上使用注释启用基于注释的安全性。
- 例如,以下内容将启用Spring Security的@Secured注释。
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
// ...
}
- 然后,在方法(类或接口)上添加注释会相应地限制对该方法的访问。Spring Security的本机注释支持为该方法定义了一组属性。这些将传递给AccessDecisionManager,以便做出实际决定:
public interface BankService {
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();
@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}
- 允许应用简单的基于角色的约束
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}
- GlobalMethodSecurityConfiguration
- 可能需要执行比@EnableGlobalMethodSecurity注释允许更复杂的操作。对于这些实例,您可以扩展GlobalMethodSecurityConfiguration确保@EnableGlobalMethodSecurity子类上存在注释。
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
// ... create and return custom MethodSecurityExpressionHandler ...
return expressionHandler;
}
}
- 可能需要执行比@EnableGlobalMethodSecurity注释允许更复杂的操作。对于这些实例,您可以扩展GlobalMethodSecurityConfiguration确保@EnableGlobalMethodSecurity子类上存在注释。
后处理配置对象
- ObjectPostProcessor可用于修改或替换Java Configuration创建的许多Object实例。例如,如果要配置filterSecurityPublishAuthorizationSuccess属性,FilterSecurityInterceptor可以使用以下命令:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor() {
public O postProcess(
O fsi) {
fsi.setPublishAuthorizationSuccess(true);
return fsi;
}
});
}
获取关于当前用户的信息
- 在SecurityContextHolder我们内部存储当前与应用程序交互的主体的详细信息。Spring Security使用Authentication对象来表示此信息。您通常不需要自己创建Authentication对象,但用户查询Authentication对象是相当常见的。您可以使用以下代码块(从应用程序的任何位置)获取当前经过身份验证的用户的名称,例如:
-
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}- 调用返回的对象getContext()是SecurityContext接口的实例。这是保存在线程本地存储中的对象。
-
- Spring Security 安全拦截器和安全对象模型
- 1.AbstractSecurityInterceptor 为处理安全对象请求提供了一致的工作流程,通常:
- 查找与当前请求关联的“配置属性”
- “配置属性”可以被认为是对所使用的类具有特殊含义的String AbstractSecurityInterceptor。
- 它们由ConfigAttribute框架内的接口表示。它们可能是简单的角色名称,也可能具有更复杂的含义,具体取决于实现的复杂程度AccessDecisionManager。
- 在AbstractSecurityInterceptor配置了SecurityMetadataSource它用来查找属性的安全对象。
- 将安全对象,当前Authentication和配置属性提交AccessDecisionManager给授权决策
- (可选)更改Authentication调用发生的位置
- 允许安全对象调用继续(假设已授予访问权限)
- 调用AfterInvocationManager如果配置,一旦调用返回。如果调用引发了异常,AfterInvocationManager则不会调用该异常。
- 查找与当前请求关联的“配置属性”
- 2.RunAsManager
- 假设AccessDecisionManager决定允许请求,AbstractSecurityInterceptor通常只会继续请求。
- 3.AfterInvocationManager
- 在安全对象调用继续进行然后返回 - 这可能意味着方法调用完成或过滤器链继续进行 - AbstractSecurityInterceptor获得最后一次机会来处理调用。
- 1.AbstractSecurityInterceptor 为处理安全对象请求提供了一致的工作流程,通常:
上一篇: Vue快速入门
下一篇: SQL将一个表转化成建表语句的存储过程