《Spring实战》学习笔记-------Security框架

文章目录

    • 1. Spring Security的模块
    • 2. 初步配置安全性,过滤Web请求(Filter)*****
    • 3. 配置用户存储
      • 3.1 基于内存的用户存储
      • 3.2 基于数据库的用户存储
      • 3.3 配置自定义的用户服务**
    • 4. 拦截请求****
    • 5. 强制使用Https
    • 6. 启用Remember-me功能

Spring Security是基于Spring AOP和Serlet规范中的Filter实现的安全框架。它使用Filter保护Web请求并限制URL级别的访问;使用AOP保护方法调用,确保只有具备适当权限的用户才能访问保护的方法。

1. Spring Security的模块

Spring Security 3.2分为11个模块

模块 描述
ACL 通过访问控制列表(access control list)为域对象提供安全性
切面 当使用Spring Security注解时,会使用基于AspectJ的切面,而不是标准的SpringAOP
配置 包含通过Java和XML配置Spring Security功能支持
LDAP 支持基于LDAP进行认证
标签库 Spring Security的JSP标签库
Web 提供了Spring Security基于Filter的Web安全性支持
核心 提供Spring Security基本库
加密 提供加密和密码编码的功能
OpenID 支持使用OpenId进行集中认证
Remoting 提供对Spring Remoting的支持
CAS客户端 提供与Jasig的中心认证服务(central authentication service)进行集成的功能

2. 初步配置安全性,过滤Web请求(Filter)*****

先注册DelegatingFilterProxy:

package spittr.config;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
//继承这个类来注册DelegatingFilterProxy,方法体为空。
public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer {}

AbstractSecurityWebApplicationInitializer实现了WebApplicationInitializer接口,因此Spring会发现它,并用它在容器中注册DelegatingFilterProxy。DelegatingFilterProxy会拦截发往应用中的请求,将它交给Filter。


启用Web安全性功能配置:

package spittr.config;
@Configuration
@EnableWebSecurity //启用 Web安全性
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}

不重载任何方法,应用是锁定状态,没有人能进入系统。**所有的请求都需要认证,但是没有用户存储,请求都会认证失败。**我们可以通过重载configure()方法来配置Web安全性:

方法 描述
configure(WebSecurity) 配置Spring Security的Filter链
configure(HttpSecurity) 配置如何通过拦截器保护请求
configure(AuthenticationManagerBuilder) 配置user-detail服务

要想实现应有的作用,还需要:

  • 配置用户存储
  • 指定需要认证的请求

3. 配置用户存储

通过重载configure(AuthenticationManagerBuilder auth)方法来实现。

3.1 基于内存的用户存储

这种存储多用于调试和开发测试。调用 auth.inMemoryAuthentication().

3.2 基于数据库的用户存储

注入一个DataSource类型的dataSource,调用auth.jdbcAuthentication().dataSource(dataSource)方法。可以通过该方法下的.usersByUsernamQuery()来自定义查询用户。
其余的查询方法和密码加密详见配置用户存储。

3.3 配置自定义的用户服务**

如果需要认证的用户存储在非关系型数据库,或者我们想用自己的方法从数据库中取出用户再进行认证。这种情况下,可以自定义一个实现UserDetailsService接口的类,它以提供一个组件(能插入到SecurityConfig配置类中)。

//UserDetailsService接口代码
public interface UserDetailsService{
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
	//UserDetails 是org.springframework.security.core.userdetails下的一个接口。
}

例如,我们要从SpitterRepository中查找具体的Spittle,来验证信息:

package spittr.security;

public class SpitterUserService implements UserDetailsService {
	
	private final SpittleRepository spittleRepository;

	public SpitterUserService(SpittleRepository spittleRepository) {//注入自定义的服务
		this.spittleRepository = spittleRepository;
	}

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// TODO Auto-generated method stub
		Spittle spittle = spittleRepository.findSpittleByUsername(String username); //调用服务查找该username的用户
		if(spittle != null) { //如果该用户存在,就创建权限列表,然后返回
			List<GrantedAuthority> authorities = new ArrayList<>();
			authorities.add(new SimpleGrantedAuthority("ROLE_SPITTER"));
			
			return new org.springframework.security.core.userdetails.User(spittle.getUsername(), spittle.getPassword(), authorities);
			//返回一个User对象,存储着这个用户的信息和权限
		}else {
			throw new UsernameNotFoundException("User '" + username + "' not found");
			//不存在就抛出异常
		}
		return null;
	}
}

这个查询username对应的用户信息的部件已经写好了,要使它发挥作用,需要将它设置到安全配置类(SecurityConfig)中去:

package spittr.config;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Autowired
	SpittleRepository spittleRepository;
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception{
		auth.userDetailsService(new SpitterUserService(spittleRepository));
	}
}

userDetailsService()方法(类似于inMemoryAuthentication()和jdbcAuthentication())会配置一个用户存储。
另一种方案是使Spittle直接实现UserDetails接口,这样loadUserByUsername()就能直接返回Spitter对象,而不必通过User对象传递返回。


4. 拦截请求****

通过重载configure(HttpSecurity)方法实现对请求进行细粒度安全性控制。

例如,为不同的URL路径有选择的应用安全性:

//在SecurityConfig类中
protected void configure(HttpSecurity http) throws Exception{
		http
			.authorizeRequests()   //配置请求级别的安全性细节
				.antMatchers("/spitters/me").authenticated()  //指明对"/spitters/me"路径的请求需要认证
				.antMatchers(HttpMethod.POST, "/spittles").authenticated()  //指明对"/spittles"路径的POST请求必须经过认证
				.anyRequest().permitAll(); //其它请求都是允许的,不需要认证和权限
				
		//这些规则会按照给定的顺序发挥作用,所以anyRequest().permitAll要写在最后!!!
	}

antMatchers()方法可以改写成通配符的方式antMatchers("/spitters/**").authenticated();或指定多个路径antMatchers("/spitters/**", "/spittle/me").authenticated();

authenticated()要求在执行请求时,必须已经登录了应用。如果用户没有进行认证,Filter就会拦截该请求。
permitAll()允许请求没有任何的安全限制。


除了authenticated()和permitAll()还有其它的方法:

方法 功能
access(String) 如果给定的SpEL表达式结果为true,就允许访问
anonymous() 允许匿名用户访问
authenticated() 允许认证过的用户访问
denyAll() 拒绝所有访问
fullyAuthenticated 用户是完整认证(不是Remember-me认证的)允许访问
hasAnyAuthority(String…) 如果用户具备给定权限中的某一个的话,允许访问
hasAnyRole(String…) 如果用户具备给定角色中的某一个的话,允许访问
hasAuthority(String) 如果用户具备给定的权限,允许访问
hasRole(String) 如果用户具备给定的角色,允许访问
hasIpAddress(String) 如果请求来自给定Ip,允许访问
not() 对其它访问方法的结果取反
permitAll() 允许访问
remenberMe() 如果用户是通过Remember-me功能认证的,允许访问

5. 强制使用Https

//在SecurityConfig类中
protected void configure(HttpSecurity http) throws Exception{
		http
			.authorizeRequests()   
				.antMatchers("/spitters/me").authenticated()  
				.antMatchers(HttpMethod.POST, "/spittles").authenticated()  
				.anyRequest().permitAll() 
			.and().requiresChannel() //使用通道
				.antMatchers("/spitter/form").requiresSecure(); //为这个路径使用 Https通道
	    //http.requiresChannel().antMatchers("/").requiresInsecure(); //使用 Http通道
	}

requiresSecure()使用Https,requiresInsecure()使用Http


6. 启用Remember-me功能

在SecurityConfig配置类中配置该功能非常简单:

protected void configure(HttpSecurity http) throws Exception{
		http...//其它设置
		
		http.rememberMe() //这个功能是通过在cookie中存储一个token完成的
			.tokenValiditySeconds(2419200) //指定这个token四周有效,默认是两周
			.key("spitterKey");//这里将私钥的key设置为spitterKey,虽然我也不知道怎么用
			//token包含用户名、密码、过期时间、私钥,写入cookie时已经进行了MD5哈希
	}

要实现这个功能,还需要前端页面的登陆表单中包含一个name为"remember_me"的参数


在Remember-me生效期间退出的功能,Filter已经默认实现了,Filter会拦截对“/logout”的请求。因此,为应用添加退出功能只需在页面中添加链接:

<a href="/logout"> Logout a>
<a th:href="@{/logout}"> Logout a> 

点击后、Remember-me的token会被清除,浏览器会重定向到"/login?logout"。
如果想定位到其它页面,可以增加配置:

http.formLogin() 
		.loginPage("/login") //设置的登录页面
	.and()
		.logout().logoutSuccessUrl("/"); //设置登出成功后返回的页面

你可能感兴趣的:(Spring)