Spring Security 快速入门

栏目: Java · 发布时间: 6年前

内容简介:我曾经使用这里大家可以参考Spring Security的官方介绍文档:简单的来说:

我曾经使用 Interceptor 实现了一个简单网站Demo的登录拦截和Session处理工作,虽然能够实现相应的功能,但是无疑Spring Security提供的配置方法更加简单明确,能够更好的保护Web应用。

Spring Security的相关结构

这里大家可以参考Spring Security的官方介绍文档: spring-security-architecture

简单的来说:

  • Spring Security是一个单一的 Filter ,其具体的类型是 FilterChainProxy ,其是作为 @BeanApplicationContext 中配置的。
  • 从容器的角度来看,Spring Security是一个单一的Filter,但是在其中有很多额外的Filter,每一个都扮演着他们各自的角色,如下图所示:
    Spring Security 快速入门
  • Spring Security的身份验证,主要由 AuthenticationManager 这个接口完成,其验证的主要方法是 authenticate()
public interface AuthenticationManager {   
    
  Authentication authenticate(Authentication authentication)   
    throws AuthenticationException;   
   
}
复制代码
  • 该方法可以完成三件事:
    • 如果它可以验证输入代表一个有效的主体,就返回一个 Authentication (通常包含 authenticated=true
    • 如果它可以验证输入代表一个无效的主体,就throw一个 AuthenticationException
    • 如果它不能决断,就返回 null
  • 最常用的 AuthicationManager 的实现是 ProviderManager ,它将其委托给 AuthticationProvider 这个实例, AuthenticationProviderAuthenticationManager 有一点像,但是含有一些额外的方法,来允许调用者来查询是否支持该 Authenticaion 形式。
public interface AuthenticationProvider {   
   
	Authentication authenticate(Authentication authentication)   
			throws AuthenticationException;   
   
	boolean supports(Class<?> authentication);   
   
}
	
复制代码

supports() 方法中的 Class<?> 参数是 Class<? extends Authentication> ,它只会询问其是否支持传递给 authenticate() 方法。

  • 在同一个程序中,一个 ProviderManager 通过委托一系列的 AuthenticaitonProviders ,以此来支支持多个不同的认证机制,如果 ProviderManager 无法识别一个特定的 Authentication 实例类型,则会跳过它。

  • 很多时候,一个程序含有多个资源保护逻辑组,每一个组都有他们独有的 AuthenticationManager ,通常他们共享父级,那么父级就成为了了一个 "global"资源 ,作为所有 provider 的后背。

    Spring Security 快速入门
  • Spring Security提供了一些配置帮助我们快速的开启验证功能,最常用的就是 AuthenticationManagerBuiler ,它在内存(in-memory)、JDBC、LDAP或者个人定制的 UserDetailService 这些领域都很擅长。

使用Spring Security实现访问和权限控制

注意:本后续代码以SpringBoot为框架实现,其DEMO Git:Spring-Security-Demo

  • 主要通过重载WebSecurityConfigurerAdapter的configure方法进行访问和权限控制
方法 描述
configure(WebSecurity) 通过重载,配置Spring Security的Filter链
configure(HttpSecurity) 通过重载,配置如何拦截器保护请求
configure(AuthenticationManagerBuilder) 通过重载,配置user-detail服务
  • 我们重写如下方法:
@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
		.authorizeRequests()
		.antMatchers("/index").hasAnyAuthority("ROLE_USER","ROLE_ADMIN")
		.antMatchers("/oss").hasAuthority("ROLE_ADMIN")
		.antMatchers(HttpMethod.GET, "/login").permitAll()
		.anyRequest().authenticated()
		.and()
		.formLogin()
		.loginPage("/login")
		.permitAll()//.successHandler(successHandler)
		.and()
		.logout()
		.logoutSuccessUrl("/")
		.permitAll();
	}

	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
				.withUser("root").password(new BCryptPasswordEncoder().encode("root")).roles("USER","ADMIN").and()
				.withUser("normal").password(new BCryptPasswordEncoder().encode("normal")).roles("USER");
		//auth.authenticationProvider(userProvider);
		//auth.authenticationProvider(afterProvider);
		
	}
复制代码
- 通过`antMatchers()`进行URL匹配,再进行相应的处理,比如见上代码,我们将**/index**和**/oss**两个链接进行了拦截,并分别要求拥有`ROLE_USER`或`ROLE_ADMIN`、`ROLE_ADMIN`这两个身份才能访问。
- `anyRequest().authenticated()`指其他请求都会需要验证
- `formLogin()`使其有了登录页面,如果没有后面的`loginPage()`,则会默认生成一个Spring Security的页面,而后面注释掉的`successHandler`则是后续会讲到的。
- `permitAll()`则表示当前连接不需要认证。
- `logout()`会拦截所以的**\logout**请求,完成登出操作,`logoutSuccessUrl()`则是登出后的重定向地址。
- `and()`在其中起连接作用。
复制代码
  • 一些常用的保护路径配置方法

    • authenticated() : 允许认证过的用户访问
    • denyAll() : 无条件拒绝所有访问
    • fullyAuthenticated() : 如果用户是完整认证(不通过Remeber me)访问
    • hasIpAdress(String) : 如果骑牛来自给定IP地址,就可以访问
    • hasAnyAuthority(String ...) : 如果用于具备任意一个给定角色,就可以访问
    • hasAnthority(String) : 如果用户具备给定角色,就可以访问
    • permitAl() : 无条件允许方法
    • remeberMe():如果用户是通过Remeber-me认证的,就可以访问
    • 另外,与Autheority对应有一个Role,两者是一个概念,Autheority必须以“ROLE_”开头,而Role不需要,见上代码。
  • 则此时我们的root账号既能够访问index也能够访问oss,而normal账号只能访问index,不能访问oss,如果访问oss会出现:

    There was an unexpected error (type=Forbidden, status=403).

  • 上面我们通过重载configure(AuthenticationManagerBuilder auth)生成了两个内存用户root和normal,我们也可以通过jdbc等方法实现。

通过AuthenticationSuccessHandler实现认证成功后的处理

  • 通过实现AuthenticationSuccessHandler接口,我们可以在验证成功后执行相应的代码,比如 Token 的设置等等,比如我现在打印一条登录信息,并将请求重定向到首页
@Component
public class SuccessHandler implements AuthenticationSuccessHandler{

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {
		System.out.println(authentication.getName()+" is loging , role is"+authentication.getAuthorities());
		response.sendRedirect("/");
		
	}

复制代码
  • 并将其添加到 formLogin() 后,即:
.formLogin()
		.loginPage("/login")
		.permitAll().successHandler(successHandler)

复制代码
  • 再次登录root账户,则会在控制台看到: root is loging , role is[ROLE_ADMIN, ROLE_USER]

通过AuthenticationProvider实现个性化认证

  • 我们建立一个 UserAuthProvider ,并让其实现 AuthenticationProvider 接口:
@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		
		System.out.println("-----------------------------------------------------------------------");
		System.out.println("This is UserAuthProvider");
		
		System.out.println("starting authenticate ... ...");
		System.out.println("Credentials:"+authentication.getCredentials());
		System.out.println("Name:"+authentication.getName());
		System.out.println("Class:"+authentication.getClass());
		System.out.println("Details:"+authentication.getDetails());
		System.out.println("Principal:"+authentication.getPrincipal());
		System.out.println("-----------------------------------------------------------------------");
		UsernamePasswordAuthenticationToken auth=new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials());
		return auth;
	}

	@Override
	public boolean supports(Class<?> authentication) {
		System.out.println("This is UserAuthProvider");
		System.out.println("starting supports");
		System.out.println(authentication.getClass());
		return false;
	}
复制代码
  • 同时,我们注释掉以前的 auth.inMemoryAuthentication() ,将UserAuthProvider加入到 AuthenticationManagerBuilder 中,即:
@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//		auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
//				.withUser("root").password(new BCryptPasswordEncoder().encode("root")).roles("USER","ADMIN").and()
//				.withUser("normal").password(new BCryptPasswordEncoder().encode("normal")).roles("USER");
		auth.authenticationProvider(userProvider);
		auth.authenticationProvider(afterProvider);
		
	}


复制代码
  • 此时我们再次登录,会发现控制台会输出
This is UserAuthProvider   
	starting supports  
	 java.lang.  Class 
复制代码
  • 其原因是我们重写的 supports() 方法,永远返回false,而返回false时,即不会再调用 authenticate() 进行认证操作(正如上面所介绍的),我们将 supports() 的返回值变成true,再次登录(username: root password: 1234),则控制台会输出
This is UserAuthProvider
starting supports
class java.lang.Class
-----------------------------------------------------------------------
This is UserAuthProvider
starting authenticate ... ...
Credentials:1234
Name:root
Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Details:org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node04v47liue6knt1oghnzgiqb9dx0
Principal:root
-----------------------------------------------------------------------
root is loging , role is[]

复制代码
  • 即成功登录了,因为我们在 authenticate() 方法中直接声明了一个 Authentication 的实例 UsernamePasswordAuthenticationToken ,并返回了,正如上面所说,当返回 Authentication 实例时,则默认为授权成功,而如果我们返回 null ,则说明无法判断,不会登录成功。

  • 此时我们再创建一个对象 UserAfterProvider ,其也实现 AuthenticationProvider 接口,并将 UserAfterProviderUserAuthProviderauthenticate() 返回值都设置为 null ,我们再次使用上面的数据进行登录,控制台输出如下:

This is UserAuthProvider
starting supports
class java.lang.Class
-----------------------------------------------------------------------
This is UserAuthProvider
starting authenticate ... ...
Credentials:1234
Name:root
Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Details:org.springframework.security.web.authentication.WebAuthenticationDetails@43458: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node01m47f3t6xq5a470fu07jaipzb0
Principal:root
-----------------------------------------------------------------------
This is UserAfterProvider
starting supports
class java.lang.Class
-----------------------------------------------------------------------
This is UserAfterProvider
starting authenticate ... ...
Credentials:1234
Name:root
Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Details:org.springframework.security.web.authentication.WebAuthenticationDetails@43458: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node01m47f3t6xq5a470fu07jaipzb0
Principal:root
-----------------------------------------------------------------------

复制代码
  • 即两个Porvider都进行了验证,都没有通过(返回null),说明所有加入 AuthenticationManagerBuilder 的验证都会进行一遍,那么如果我们将其中一个Provider的 authenticate() 返回值还原为 Authentication 实例,再次登录,则控制台会输出如下结果:
This is UserAuthProvider
starting supports
class java.lang.Class
-----------------------------------------------------------------------
This is UserAuthProvider
starting authenticate ... ...
Credentials:1234
Name:root
Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Details:org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node04v47liue6knt1oghnzgiqb9dx0
Principal:root
-----------------------------------------------------------------------
root is loging , role is[]
This is UserAuthProvider
starting supports
class java.lang.Class
-----------------------------------------------------------------------
This is UserAuthProvider
starting authenticate ... ...
Credentials:null
Name:root
Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Details:org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node04v47liue6knt1oghnzgiqb9dx0
Principal:root
-----------------------------------------------------------------------
复制代码
  • 因为我们重写了 AuthenticationSuccessHandler ,所以验证成功后悔重定向到**/ ,而我Controller里对 / 又做了一次重定向到 /index**,所以发生了两次验证,而这次我们发现因为 UserAuthProvider 通过了,所以 UserAfterProvider 并没有进行验证,所以我们可以知道,只要有一个Provider通过了验证我们就可以认为通过了验证。

  • 因此,我们可以通过实现 AuthenticationProvider 来写入自己的一些认证逻辑,甚至可以@Autowire相关Service来辅助实现。


以上所述就是小编给大家介绍的《Spring Security 快速入门》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Clean Architecture

Clean Architecture

Robert C. Martin / Prentice Hall / 2017-9-20 / USD 34.99

Practical Software Architecture Solutions from the Legendary Robert C. Martin (“Uncle Bob”) By applying universal rules of software architecture, you can dramatically improve developer producti......一起来看看 《Clean Architecture》 这本书的介绍吧!

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具