接着上节的讲,在添加了@EnableWebSecurity注解后,如果需要自定义一些配置,则需要和继承WebSecurityConfigurerAdapter后,覆盖某些方法。

我们来看一下WebSecurityConfigurerAdapter中哪些方法可以重写,需要重写。

(1)WebSecurity

默认是一个空方法,一般也不会再重写。

    public void configure(WebSecurity web) throws Exception { }

(2)HttpSecurity

默认的父类代码默认任何request都需要认证,使用默认的login page基于表单认证,使用HTTP基本认证。

    protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}

下面是一些自定义写法。

    @Override
protected void configure(HttpSecurity http) throws Exception {
//@formatter:off
http.authorizeRequests()
// all users have access to these urls
.antMatchers("/resources/**", "/signup", "/about").permitAll()
// Any URL that starts with "/admin/" will be restricted to users who have the role "ROLE_ADMIN"
.antMatchers("/admin/**").hasRole("ADMIN")
// Any URL that starts with "/db/" requires the user to have both "ROLE_ADMIN" and "ROLE_DBA"
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
// Any URL that starts with "/group_a/" requires the user to have both "ROLE_ADMIN" or "ROLE_GROUP_A"
.antMatchers("/admin/**").hasAnyRole("ADMIN", "GROUP_A")
// Any URL that has not already been matched on only requires that the user be authenticated
.anyRequest().authenticated()
.and().formLogin()
// all users have access to custom login page
.loginPage("/login").permitAll()
.and().logout()
// customize logout url
.logoutUrl("/my/logout")
// customize logout success url
.logoutSuccessUrl("/my/index")
// specify a custom LogoutSuccessHandler. If this is specified, logoutSuccessUrl() is ignored
.logoutSuccessHandler(logoutSuccessHandler)
// invalidate the HttpSession at the time of logout. This is true by default
.invalidateHttpSession(true)
// Adds a LogoutHandler. SecurityContextLogoutHandler is added as the last LogoutHandler by default
.addLogoutHandler(logoutHandler)
// Allows specifying the names of cookies to be removed on logout success
.deleteCookies()
.and().rememberMe()
// Add remember me function and valid date.
.key("uniqueAndSecret")
.tokenValiditySeconds(60 * 60 * 24 * 7);
//@formatter:on
}

(3)AuthenticationManagerBuilder

默认是这样写的:

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
this.disableLocalConfigureAuthenticationBldr = true;
}

由上一节分析可知,它其实默认使用DefaultPasswordEncoderAuthenticationManagerBuilder这个Builder及自动配置的UserDetails和UserDetailsService。

    protected AuthenticationManager authenticationManager() throws Exception {
if (!authenticationManagerInitialized) {
// [1]如果覆盖configure()方法,则disableLocalConfigureAuthenticationBldr为false
// [2]如果是默认的configure()方法,disableLocalConfigureAuthenticationBldr还是true
configure(localConfigureAuthenticationBldr);
if (disableLocalConfigureAuthenticationBldr) {
authenticationManager = authenticationConfiguration
.getAuthenticationManager(); // [2]
}
else {
authenticationManager = localConfigureAuthenticationBldr.build(); // [1]
}
authenticationManagerInitialized = true;
}
return authenticationManager;
}

如果被覆盖,虽然还是使用的DefaultPasswordEncoderAuthenticationManagerBuilder,但是我们可以使用UserDetailsManagerConfigurer(的两个子类InMemoryUserDetailsManagerConfigurer,JdbcUserDetailsManagerConfigurer)来构建UserDetailsService及UserDetails。以InMemoryUserDetailsManagerConfigurer为例,下面是自定义的写法。

    @Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
} @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//@formatter:off
// returns InMemoryUserDetailsManagerConfigurer
PasswordEncoder encoder = new BCryptPasswordEncoder();
auth.inMemoryAuthentication()
// create a UserDetailsBuilder and add to userBuilders
.withUser("user").password("{bcrypt}" + encoder.encode("pass")).roles("USER")
// returns InMemoryUserDetailsManagerConfigurer
.and()
// create a UserDetailsBuilder again and add to userBuilders
.withUser("admin").password("{bcrypt}" + encoder.encode("pass")).roles("USER", "ADMIN");
//@formatter:on
}

[注] 框架要求密码必须加密,所以这里加了有关password encode的支持。

那么这段代码如何生成UserDetailsService及UserDetails的呢?流程如下:

[1] 调用AuthenticationManagerBuilder的inMemoryAuthentication()方法创建InMemoryUserDetailsManagerConfigurer,调用InMemoryUserDetailsManagerConfigurer的构造器时则会创建InMemoryUserDetailsManager(即UserDetailsService的实现类),最终经过层层父类(InMemoryUserDetailsManagerConfigurer -> UserDetailsManagerConfigurer -> UserDetailsServiceConfigurer -> AbstractDaoAuthenticationConfigurer)设定到AbstractDaoAuthenticationConfigurer中。

    public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
throws Exception {
return apply(new InMemoryUserDetailsManagerConfigurer<>());
}
    public InMemoryUserDetailsManagerConfigurer() {
super(new InMemoryUserDetailsManager(new ArrayList<>()));
}
    protected AbstractDaoAuthenticationConfigurer(U userDetailsService) {
this.userDetailsService = userDetailsService;
provider.setUserDetailsService(userDetailsService);
if (userDetailsService instanceof UserDetailsPasswordService) {
this.provider.setUserDetailsPasswordService((UserDetailsPasswordService) userDetailsService);
}
}

[2] 调用AuthenticationManagerBuilder的apply()方法设定defaultUserDetailsService为[1]的InMemoryUserDetailsManager并且把[1]的InMemoryUserDetailsManagerConfigurer加到父类AbstractConfiguredSecurityBuilder的configurers list中

    private <C extends UserDetailsAwareConfigurer<AuthenticationManagerBuilder, ? extends UserDetailsService>> C apply(
C configurer) throws Exception {
this.defaultUserDetailsService = configurer.getUserDetailsService();
return (C) super.apply(configurer);
}

[3] 调用InMemoryUserDetailsManagerConfigurer的父类UserDetailsManagerConfigurer的withUser()方法生成多个UserDetailsBuilder放在userBuilders list中

    public final UserDetailsBuilder withUser(String username) {
UserDetailsBuilder userBuilder = new UserDetailsBuilder((C) this);
userBuilder.username(username);
this.userBuilders.add(userBuilder);
return userBuilder;
}

[4] 当调用DefaultPasswordEncoderAuthenticationManagerBuilder的build()方法时,则会调用

[4.1] 调用UserDetailsServiceConfigurer的configure()方法

    @Override
public void configure(B builder) throws Exception {
initUserDetailsService();
super.configure(builder);
}

[4.2] 调用UserDetailsManagerConfigurer的initUserDetailsService()方法通过[3]的userBuilders创建User对象(UserDetails的实现类,并且从[1]中的AbstractDaoAuthenticationConfigurer获取UserDetailsService,并把UserDetails放到UserDetailsService中。

    @Override
protected void initUserDetailsService() throws Exception {
for (UserDetailsBuilder userBuilder : userBuilders) {
getUserDetailsService().createUser(userBuilder.build());
}
for (UserDetails userDetails : this.users) {
getUserDetailsService().createUser(userDetails);
}
}

下面是一些自定义写法:

    @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//@formatter:off
// returns InMemoryUserDetailsManagerConfigurer
auth.inMemoryAuthentication()
// create a UserBuilder and add to userBuilders
.withUser("user").password("password").roles("USER")
// returns InMemoryUserDetailsManagerConfigurer
.and()
// create a UserBuilder again and add to userBuilders
.withUser("admin").password("password").roles("USER", "ADMIN");
//@formatter:on
}

(4)authenticationManagerBean()

我们覆盖了configure(AuthenticationManagerBuilder auth)后,我们使用了AuthenticationManagerBuilder 的实现类DefaultPasswordEncoderAuthenticationManagerBuilder,通过InMemoryUserDetailsManagerConfigurer创建自己的UserDetailsService的实现类InMemoryUserDetailsManager及User,系统还会默认给我们创建AuthenticationProvider的实现类DaoAuthenticationProvider。但是我们发现,这些对象并不是Spring Bean。所以我们可以通过覆盖该方法并且声明为一个Bean,这样就可以在项目中注入并使用这个Bean了。

    @Bean(name = "myAuthenticationManager")
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

通过父类的源码可以看到,实际上在调用时,创建了一个AuthenticationManager代理。

    public AuthenticationManager authenticationManagerBean() throws Exception {
return new AuthenticationManagerDelegator(authenticationBuilder, context);
}

(5)userDetailsServiceBean()

和(4)类似,Override this method to expose a UserDetailsService created from configure(AuthenticationManagerBuilder) as a bean. In general only thefollowing override should be done of this method:

    @Bean(name = "myUserDetailsService")
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}

(6) UserDetailsService

还记得第三章的UserDetailsService实现类是如何生成的吗?这里做一个简述:

[1] AuthenticationConfiguration中创建InitializeUserDetailsBeanManagerConfigurer Bean。

[2] build时调用InitializeUserDetailsBeanManagerConfigurer的内部类InitializeUserDetailsManagerConfigurer的configure()方法。

[3] 在ApplicationContext中获取UserDetailsService(by type),如果没有找到自定义的UserDetailsService Bean,则UserDetailsServiceAutoConfiguration生效,会lazy load一个InMemoryUserDetailsManager;反之,则使用我们自定义的UserDetailsService Bean。

在WebSecurityConfigurerAdapter中,userDetailsServiceBean()和userDetailsService()两个方法内容实际上都是一样的。都是获取当前环境中(自定义的或系统生成的InMemoryUserDetailsManager)的UserDetailsService的代理类。所以,该类一般不需要重写,如果想自定义自己的UserDetailsService,可以直接实现UserDetailsService接口,并且把该类声明为一个Spring Bean:

@Service
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// TODO Auto-generated method stub
return null;
}
}

当然你也可以直接覆盖该方法并声明为一个Bean:

    @Bean
@Override
public UserDetailsService userDetailsService() {
return (username) -> {
AppUser user = appUserRepository.findOneByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("Incorrect username or password.");
}
return user;
};
}

需要注意的是,我们也要有UserDetails的实现类供UserDetailsService处理。

Spring Security(4):自定义配置的更多相关文章

  1. Spring Security基于Java配置

    Maven依赖 <dependencies> <!-- ... other dependency elements ... --> <dependency> < ...

  2. CAS Spring Security 3 整合配置(转)

    一般来说, Web 应用的安全性包括用户认证( Authentication )和用户授权( Authorization )两个部分.用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否 ...

  3. spring boot rest 接口集成 spring security(2) - JWT配置

    Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...

  4. Spring Security(三) —— 核心配置解读

    摘要: 原创出处 https://www.cnkirito.moe/spring-security-3/ 「老徐」欢迎转载,保留摘要,谢谢! 3 核心配置解读 上一篇文章<Spring Secu ...

  5. Spring Cloud Feign 自定义配置(重试、拦截与错误码处理) 实践

    Spring Cloud Feign 自定义配置(重试.拦截与错误码处理) 实践 目录 Spring Cloud Feign 自定义配置(重试.拦截与错误码处理) 实践 引子 FeignClient的 ...

  6. spring security 3.2 配置详解(结合数据库)

    没事就来了解下spring security.网上找了很多资料.有过时的,也有不是很全面的.各种问题也算是让我碰了个遍.这样吧.我先把整个流程写下来,之后在各个易混点分析吧. 1.建立几个必要的页面. ...

  7. spring security 3 自定义认证,授权示例

    1,建一个web project,并导入所有需要的lib. 2,配置web.xml,使用Spring的机制装载: <?xml version="1.0" encoding=& ...

  8. spring security使用自定义登录界面后,不能返回到之前的请求界面的问题

    昨天因为集成spring security oauth2,所以对之前spring security的配置进行了一些修改,然后就导致登录后不能正确跳转回被拦截的页面,而是返回到localhost根目录. ...

  9. Spring Security Oauth2 的配置

    使用oauth2保护你的应用,可以分为简易的分为三个步骤 配置资源服务器 配置认证服务器 配置spring security 前两点是oauth2的主体内容,但前面我已经描述过了,spring sec ...

  10. Spring Security之动态配置资源权限

    在Spring Security中实现通过数据库动态配置url资源权限,需要通过配置验证过滤器来实现资源权限的加载.验证.系统启动时,到数据库加载系统资源权限列表,当有请求访问时,通过对比系统资源权限 ...

随机推荐

  1. Educational Codeforces Round 76 (Rated for Div. 2) D

    D题 原题链接 题意:就是给你n个怪兽有一个属性(攻击力),m个英雄,每个英雄有两种属性(分别为攻击力,和可攻击次数),当安排最好的情况下,最少的天数(每选择一个英雄出战就是一天) 思路:因为怪兽是不 ...

  2. 成功解决 AttributeError: module 'tensorflow.python.keras.backend' has no attribute 'get_graph'

    在导入keras包时出现这个问题,是因为安装的tensorflow版本和keras版本不匹配,只需卸载keras,重新安装自己tensorflow对应的版本就OK了.可以在这个网址查看tensorfl ...

  3. 通过Java调用Python脚本

    在进行开发的过程中,偶尔会遇到需要使用Java调用Python脚本的时候,毕竟Python在诸如爬虫,以及科学计算等方面具有天然的优势.最近在工作中遇到需要在Java程序中调用已经写好的Python程 ...

  4. WCF Windows基础通信

    概述 WCF,Windows Communication Foundation ,Windows通信基础, 面向服务的架构,Service Orientation Architechture=SOP ...

  5. 利用XtraBackup给MYSQL热备(基于数据文件)

    利用XtraBackup给MYSQL热备(基于数据文件) By JRoBot on 2013 年 11 月 26 日 | Leave a response 利用XtraBackup给MYSQL热备(基 ...

  6. click([[data],fn]) 触发每一个匹配元素的click事件。

    click([[data],fn]) 概述 触发每一个匹配元素的click事件. 这个函数会调用执行绑定到click事件的所有函数.大理石平台精度等级 参数 fnFunctionV1.0 在每一个匹配 ...

  7. CSP-S模拟测试 88 题解

    T1 queue: 考场写出dp柿子后觉得很斜率优化,然后因为理解错了题觉得斜率优化完全不可做,只打了暴力. 实际上他是可以乱序的,所以直接sort,正确性比较显然,贪心可证,然后就是个sb斜率优化d ...

  8. 《剑指offer》数组中只出现一次的数字

    本题来自<剑指offer> 数组中只出现一次的数字 题目: 一个整型数组里除了两个数字之外,其他的数字都出现了两次.请写程序找出这两个只出现一次的数字. 思路: 思路一:在<剑指of ...

  9. seq2seq聊天模型(三)—— attention 模型

    注意力seq2seq模型 大部分的seq2seq模型,对所有的输入,一视同仁,同等处理. 但实际上,输出是由输入的各个重点部分产生的. 比如: (举例使用,实际比重不是这样) 对于输出"晚上 ...

  10. scrapy框架之下载中间件

    介绍 中间件是Scrapy里面的一个核心概念.使用中间件可以在爬虫的请求发起之前或者请求返回之后对数据进行定制化修改,从而开发出适应不同情况的爬虫. “中间件”这个中文名字和前面章节讲到的“中间人”只 ...