- Spring Security 是一种基于Spring AOP 和Servlet 规范中的Filter 实现的安全框架
- Spring Security 充分利用了依赖注入DI 和面向切面技术
- Spring Securty 使用Servlet 规范中的Filter 保护web 请求并限制url 级别的访问。它还使用了Spring AOP 保护方法的调用——借助于对象代理和使用通知。
- 引入core,web 和config 模块
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
- 过滤web 请求,Spring Security 借助一系列Servlet Filter 来提供各种安全功能。DelegatingFilterProxy 是一个特殊的Servlet Filter,它将工作委托给一个Filter 实现类,这个实现类作为一个<bean>注入到Spring 应用上下文中。 实际上,它会拦截请求,并将请求委托给id 为springSecurityFilterChain 的bean。
- springSecurityFilterChain 本身是一个特殊的Filter,被称为FilterChainProxy,它可以链接多个其他Filter。当启动web 安全性时,会自动创建Spring Security 依赖的一系列Filter。
1、借助WebApplicationInitializer 以java 方式配置DelegatingFilterProxy,AbstractSecurityWebApplicationInitializer 实现了WebApplicationInitializer,因此Spring 会发现它,并在web 容器中注册DelegatingFilterProxy
package com.qhf.config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
/**
* #配置DelegatingFilterProxy 两种方式:
* 1.web.xml 中配置filter
* <filter>
* <filter-name>springSecurityFilterChain</filter-name>
* <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
* </filter>
*
* 2.自定义类来继承AbstractSecurityWebApplicationInitializer
* AbstractSecurityWebApplicationInitializer 实现了WebApplicationInitializer,
* ~故spring 会发现它,并且用它在web 容器中注册DelegatingFilterProxy。
*
* DelegatingFilterProxy 会拦截发往应用中的请求,并将请求委托给id 为springSecurityFilterChain 的bean
*/
public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer {
}
2、启动web 安全性功能,当然需要重写三个方法
- @EnableWebMvcSecurity 注解配置了一个参数解析器,处理器方法能通过带有@AuthenticationPrincipal 注解的参数获得认证用户的principal 或username。它还同时配置了一个bean,当使用Spring表单绑定标签库来定义表单时,这个bean 会自动添加一个隐藏的跨站请求伪造(CSRF) token 输入域。
@Configuration
@EnableWebSecurity //@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
3、重写的三个方法
- configure(WebSecurity):配置Filter链
- configure(HttpSecurity):配置如何通过拦截器保护请求
- configure(AuthenticationManagerBuilder):配置user-detail 服务
package com.qhf.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import com.qhf.dao.UserDao;
import com.qhf.service.UserServiceImpl;
/**
* #SpringSecurity 必须配置在一个
* 实现了WebSecurityConfigurer 或者继承WebSecurityConfigurerAdapter 的bean 中
*
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//需要重载3个方法,默认使用的Filter 链指定了所有http 请求都需要进行认证,当我们没有用户时,请求存储用户是不可能的,所以没有人能登陆成功
DataSource dataSource = null;
@Autowired
UserDao userDao;
/**
* #配置用户存储
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/*
* 1.配置内存级别的用户存储
* roles() 方法是authorities() 方法的简写,roles() 方法会在给定的值前面添加一个“ROLE_”前缀,并将其作为权限授予用户。
*/
auth.inMemoryAuthentication()
.withUser("user").password("123").roles("USER")
.and()
.withUser("admin").password("234").roles("USER", "ADMIN");
/*
* 2.配置基于jdbc 数据库级别的用户存储
* passwordEncoder() 中参数接收实现PasswordEncoder 接口的任意实现类, spring
* 提供了StandardPasswordEncoder,BCryptPasswordEncoder,NoOpPasswordEncoder
* 我们可以自定义类实现PasswordEncoder 接口,并且通过接口定义的matches 方法来比对数据库取出的加密后密码和请求中的密码是否一致
* 认证查询,权限查询,群组权限查询
*/
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select username, password from table where username = ?")
.authoritiesByUsernameQuery("select usrname, 'ROLE_USER' from table where username = ?")
.passwordEncoder(new StandardPasswordEncoder("abc123"));
/*
* 3.基于LDAP 进行认证,暂时没学习到
* auth.ldapAuthentication() .userSearchBase("ou=people")
* .userSearchFilter("(uid={0})") .groupSearchBase("ou=groups")
* .groupSearchFilter("member={0}")
* .contextSource().root("dc=habuma,dc=com").ldif("classpath:users.ldif");
*/
/*
* 4.当使用nosql 型数据库时,可以自定义
* spring security 提供一个UserDetailService 接口,我们自定义的service 需要
* 实现它的loadUserByUsername 方法。
*/
auth.userDetailsService(new UserServiceImpl(userDao));
}
/**
*
*/
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
/**
* 对每个请求进行细粒度安全性控制
* 注意将最为具体的请求路径放在前面,不具体的放在后面(如anyRequest)
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//input 中name=“remember-me”
http.rememberMe() //通过cookie 存储token(包含用户名,密码,过期时间,私匙且都md5哈希加密)
.tokenValiditySeconds(2419200) //有效时间
.key("myKey") //私匙
.and()
.logout()
.logoutUrl("/logout") //默认访问的登出路径
.logoutSuccessUrl("/"); //登出重定向
//匹配路径.如何保护
http.formLogin() //启用默认的登陆页面
.loginPage("/login")
.and()
.authorizeRequests() //拦截请求
.antMatchers("/user/me").access("hasRole('Role_USER') and hasIpAddress('192.168.1.2')") //springEL表达式
.regexMatchers("/user/.*").authenticated() //认证:authenticated 表示必须已登陆
.antMatchers(HttpMethod.POST, "/user").hasRole("USER") //权限:自动加前缀ROLE_
.anyRequest().permitAll()
.and()
.requiresChannel() //强制通道的安全性
.antMatchers("/user/insertUser").requiresSecure() //要求https 安全通道
.antMatchers("/").requiresInsecure(); //如果通过https 发送了对 / 的请求,会自动把请求重定向到不安全的http 通道上
/*
* 跨站请求伪造csrf:一个站点欺骗用户提交请求到其他服务器,默认开启。
* spring security 通过一个同步token 的方式来实现csrf 防护功能。
* 它会拦截非get、head、options、trace 的请求并检查csrf token,不存在或不匹配则抛csrf 异常
* 我们要在表单中 name="_csrf" 的input域中提交token
*/
http.csrf().disable();
}
}
4、业务实现
package com.qhf.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import com.qhf.dao.UserDao;
import com.qhf.entity.User;
public class UserServiceImpl implements UserDetailsService {
UserDao userDao;
public UserServiceImpl(UserDao userDao) {
super();
this.userDao = userDao;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.selectByUsername(username);
if(user != null) {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));//添加权限列表
return new org.springframework.security.core.userdetails
.User(user.getUsername(), user.getPassword(), authorities);
}
throw new UsernameNotFoundException("User '" + username + " ' not found");
}
}