前言
本文的源码地址
主要实现 Spring Security
的安全认证,结合 RESTful API
的风格,使用无状态的环境。
主要实现是通过请求的 URL ,通过过滤器来做不同的授权策略操作,为该请求提供某个认证的方法,然后进行认证,授权成功返回授权实例信息,供服务调用。
基于Token的身份验证的过程如下:
- 用户通过用户名和密码发送请求。
- 程序验证。
- 程序返回一个签名的token 给客户端。
- 客户端储存token,并且每次用于每次发送请求。
- 服务端验证token并返回数据。
每一次请求都需要token,所以每次请求都会去验证用户身份,所以这里必须要使用缓存,
基本使用
加入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
了解基础配置
认证的基本信息
public interface UserDetails extends Serializable {
//返回分配给用户的角色列表
Collection<? extends GrantedAuthority> getAuthorities();
//返回密码
String getPassword();
//返回帐号
String getUsername();
// 账户是否未过期
boolean isAccountNonExpired();
// 账户是否未锁定
boolean isAccountNonLocked();
// 密码是否未过期
boolean isCredentialsNonExpired();
// 账户是否激活
boolean isEnabled();
}
获取基本信息
// 根据用户名查找用户的信息
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
我们只要实现这个扩展,就能够自定义方式获取认证的基本信息
WebSecurityConfigurerAdapter
WebSecurityConfigurerAdapter
提供了一种便利的方式去创建 WebSecurityConfigurer
的实例,只需要重写 WebSecurityConfigurerAdapter
的方法,即可配置拦截什么URL、设置什么权限等安全控制。
下面是主要会是要到的几个配置:
/**
* 主要是对身份认证的设置
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
this.disableLocalConfigureAuthenticationBldr = true;
}
/**
* 复写这个方法来配置 {@link HttpSecurity}.
* 通常,子类不能通过调用 super 来调用此方法,因为它可能会覆盖其配置。 默认配置为:
*
*/
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
/**
* Override this method to configure {@link WebSecurity}. For example, if you wish to
* ignore certain requests.
* 主要是对某些 web 静态资源的设置
*/
public void configure(WebSecurity web) throws Exception {
}
认证流程
阅读源码了解。
AbstractAuthenticationProcessingFilter.doFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 判断是否是需要验证方法(是否是登陆的请求),不是的话直接放过
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
// 登陆的请求开始进行验证
Authentication authResult;
try {
// 开始认证,attemptAuthentication在 UsernamePasswordAuthenticationFilter 中实现
authResult = attemptAuthentication(request, response);
// return null 认证失败
if (authResult == null) {
return;
}
// 篇幅问题,中间很多代码删了
successfulAuthentication(request, response, chain, authResult);
}
UsernamePasswordAuthenticationFilter.attemptAuthentication
// 接收并解析用户登陆信息,为已验证的用户返回一个已填充的身份验证令牌,表示成功的身份验证,
// 如果身份验证过程失败,就抛出一个AuthenticationException
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
// 方法将 request 中的 username 和 password 生成 UsernamePasswordAuthenticationToken 对象,用于 AuthenticationManager 的验证
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
ProviderManager.authenticate
验证 Authentication 对象(里面包含着验证对象)
- 如果有多个 AuthenticationProvider 支持验证传递过来的Authentication 对象,那么由第一个来确定结果,覆盖早期支持AuthenticationProviders 所引发的任何可能的AuthenticationException。 成功验证后,将不会尝试后续的AuthenticationProvider。
- 如果最后所有的 AuthenticationProviders 都没有成功验证 Authentication 对象,将抛出 AuthenticationException。
最后它调用的是 Authentication result = provider.authenticate(authentication);
只要我们自定义 AuthenticationProvider
就能完成自定义认证。
动手实现安全框架
使用的依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>