从入门到精通 SpringSecurity & Auth2.0

1 安全认证的基本概念

两个基本概念

  • 认证(Authentication)

  • 授权(Authorization)

1.1 认证

认证就是根据用户名密码登录的过程,就是所谓的登录认证 对于一些登录之后才能访问的接口(例如:查询我的账号资料),我们通常的做法是增加一层接口校验:

  • 如果校验通过,则:正常返回数据。

  • 如果校验未通过,则:抛出异常,告知其需要先进行登录。

那么,判断会话是否登录的依据是什么?先来简单分析一下登录访问流程:

  1. 用户提交 name + password 参数,调用登录接口。

  2. 登录成功,返回这个用户的 Token 会话凭证。

  3. 用户后续的每次请求,都携带上这个 Token。

  4. 服务器根据 Token 判断此会话是否登录成功。

所谓登录认证,指的就是服务器校验账号密码,为用户颁发 Token 会话凭证的过程,这个 Token 也是我们后续判断会话是否登录的关键所在。

图片

1.2 授权(鉴权)

所谓权限认证,核心逻辑就是判断一个账号是否拥有指定权限:

  • 有,就让你通过。

  • 没有,那么禁止访问!

深入到底层数据中,就是每个账号都会拥有一组权限码集合,框架来校验这个集合中是否包含指定的权限码。

例如:当前账号拥有权限码集合 ["user-add", "user-delete", "user-get"],这时候我来校验权限 "user-update",则其结果就是:验证失败,禁止访问

图片

2 Spring Security 核心组件

Spring Security 核心组件有 Authentication(认证/身份验证) 、 AuthenticationProvider(认证提供者) 、 AuthenticationManager(认证管理者) 比较重要的两个感念

  • Authentication:认证,其实就是身份的识别,也就是登录

  • Authorization:授权(鉴权),根据角色进行权限控制

2.1 凭证 Authentication

Authentication 直译是“认证”的意思,在 Spring Security 中, Authentication 接口用来表示凭证或者令牌,可以理解为用户的用户名、密码、权限等信息。

Authentication 的代码如下:

public interface Authentication extends Principal, Serializable {
//权限集合
//可使用 AuthorityUtils.commaSeparatedStringToAuthorityList("admin, ROLE_ADMIN")进行初始化
Collection<? extends GrantedAuthority> getAuthorities();
//用户名和密码认证时,可以理解为密码
Object getCredentials();
//认证时包含的一些详细信息,可以是一个包含用户信息的 POJO 实例
Object getDetails();
//用户名和密码认证时,可以理解为用户名
Object getPrincipal();
//是否认证通过,通过为 true
boolean isAuthenticated();
//设置是否认证通过
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

Spring Security常见的内置的实现类

  1. UsernamePasswordAuthenticationToken: 用户名、密码认证的场景中作为验证的凭证

  2. RememberMeAuthenticationToken: “记住我”的身份认证场景

  3. AnonymousAuthenticationToken: 匿名访问的用户

除了以上内置凭证类外,还可以通过实现 Authentication 定制自己的身份认证实现类。

2.2 认证提供者 AuthenticationProvider

AuthenticationProvider 是一个接口,包含两个函数 authenticate 和 supports,用于完成对凭证进行身份认证操作。

public interface AuthenticationProvider {
//对实参 authentication 进行身份认证操作
Authentication authenticate(Authentication authentication) throws AuthenticationException;
//判断是否支持该 authentication
boolean supports(Class<?> authentication);
}

AuthenticationProvider 接口常见内置的实现类:

  1. AbstractUserDetailsAuthenticationProvider对UsernamePasswordAuthenticationToken 类型的凭证/令牌进行验证的认证提供者类,用于“用户名+密码”验证的场景。

  2. RememberMeAuthenticationProvider: 对 RememberMeAuthenticationToken 类型的凭证/令牌进行验证的认证提供者类,用于“记住我”的身份认证场景。

  3. AnonymousAuthenticationProvider: 这是一个对 AnonymousAuthenticationToken 类型的凭证/令牌进行验证的认证提供者类,用于匿名身份认证场景。

除此之外,可以通过实现 AuthenticationProvider 接口来扩展出自定义的认证提供者。

2.3 认证管理者 AuthenticationManager

AuthenticationManager 是一个接口,其唯一的 authenticate 验证方法是认证流程的入口,接收一个 Authentication 令牌对象作为参数。

public interface AuthenticationManager {
//认证流程的入口
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

AuthenticationManager 的一个实现类名为 ProviderManager,该类有一个 providers 成员变量,负责管理一个提供者清单列表,其源码如下:

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
...
//提供者清单
private List<AuthenticationProvider> providers = Collections.emptyList();
//迭代提供者清单,找出支持令牌的提供者,交给提供者去执行令牌验证
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
...
}
}

认证管理者 ProviderManager 在进行令牌验证时,会对提供者列表进行迭代,找出支持令牌的认证提供者,并交给认证提供者去执行令牌验证。如果该认证提供者的 supports 方法返回 true,就会调用该提供者的 authenticate 方法。 如果验证成功, 那么整个认证过程结束;如果不成功, 那么继续处理列表中的下一个提供者。只要有一个验证成功, 就会认证成功。

3 Spring Security入门案例

  • pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  • HelloController

@RestController
public class HelloController {
   @GetMapping("/hello")
   public String hello() {
        return "欢迎访问 hangge.com";
   }
}
  • 启动

图片

生成密码如上图,用户名user

  • 配置用户名和密码

如果对默认的用户名和密码不满意,可以在 application.properties 中配置默认的用户名、密码和角色。

spring.security.user.name=lxs
spring.security.user.password=1
spring.security.user.roles=admin

图片

4 Spring Security进阶

4.1 简单认证流程实战

简单认证的处理流程大致包括以下步骤:

  1. 定制一个凭证/令牌类。

  2. 定制一个认证提供者类和凭证/令牌类进行配套,并完成对自制凭证/令牌实例的验证。

  3. 定制一个过滤器类,从请求中获取用户信息组装成定制凭证/令牌,交给认证管理者。

  4. 定制一个 HTTP 的安全认证配置类(AbstractHttpConfigurer 子类),将上一步定制的过滤器加入请求的过滤链。

  5. 定义一个 Spring Security 安全配置类(WebSecurityConfigurerAdapter 子类), 对 Web容器的 HTTP 安全认证机制进行配置。

案例:当系统资源被访问时,过滤器从 HTTP 的 token 请求头获取用户名和密码,然后与系统中的用户信息进行匹配,如果匹配成功, 就可以访问系统资源,否则返回 403 响应码,表示未授权。

图片

  • DemoToken

定义自定义令牌类代码如下:

public class DemoToken extends AbstractAuthenticationToken
{
//用户名称
private String userName;
//密码
private String password;
...
}
  • DemoAuthProvider 

与DemoToken匹配的验证提供者DemoAuthProvider代码如下:

public class DemoAuthProvider implements AuthenticationProvider {

public DemoAuthProvider(){
}

//模拟的数据源,实际场景从 DB 中获取
private Map<String, String> map = new LinkedHashMap<>();

//初始化模拟的数据源,放入两个用户
{
map.put("zhangsan", "123456" );
map.put("lisi", "123456" );
}

//具体的验证令牌方法
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
DemoToken token = (DemoToken) authentication;
//从数据源 map 中获取用户密码
String rawPass = map.get(token.getUserName());
//验证密码,如果不相等,就抛出异常
if (!token.getPassword().equals(rawPass))
{
token.setAuthenticated(false);
throw new BadCredentialsException("认证有误:令牌校验失败" );
}
//验证成功
token.setAuthenticated(true);
return token;
}
/**
*判断令牌是否被支持
*@param authentication 这里仅仅 DemoToken 令牌被支持
*@return
*/
@Override
public boolean supports(Class<?> authentication)
{
return authentication.isAssignableFrom(DemoToken.class);
}
}
  • DemoAuthFilter 

定制一个过滤器类DemoAuthFilter,从请求头中获取 token 字段,解析之后组装成 DemoToken 令牌实例,提交给 AuthenticationManager 进行验证。

public class DemoAuthFilter extends OncePerRequestFilter
{
//认证失败的处理器
private AuthenticationFailureHandler failureHandler = new AuthFailureHandler();
...
//authenticationManager 是认证流程的入口,接收一个 Authentication 令牌对象作为参数
private AuthenticationManager authenticationManager;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException
{
...
AuthenticationException failed = null;
try
{
Authentication returnToken=null;
boolean succeed=false;
//从请求头中获取认证信息
String token = request.getHeader(SessionConstants.AUTHORIZATION_HEAD);
String[] parts = token.split(",");
//组装令牌
DemoToken demoToken = new DemoToken(parts[0],parts[1]);
//提交给 AuthenticationManager 进行令牌验证
returnToken = (DemoToken) this.getAuthenticationManager().authenticate(demoToken);
//获取认证成功标志
succeed=demoToken.isAuthenticated();
if (succeed)
{
//认证成功,设置上下文令牌
SecurityContextHolder.getContext().setAuthentication(returnToken);
//执行后续的操作
filterChain.doFilter(request, response);
return;
}
} catch (Exception e)
{
logger.error("认证有误", e);
failed = new AuthenticationServiceException("请求头认证消息格式错误",e );
}
if(failed == null)
{
failed = new AuthenticationServiceException("认证失败");
}
//认证失败了
SecurityContextHolder.clearContext();
failureHandler.onAuthenticationFailure(request, response, failed);
}
...
}
  • AbstractHttpConfigurer 

为了使得过滤器能够生效,必须将过滤器加入 Web 容器的 HTTP 过滤处理责任链,此项工作可以通过实现一个 AbstractHttpConfigurer 配置类来完成。

public class DemoAuthConfigurer<T extends DemoAuthConfigurer<T, B>, B extends HttpSecurityBuilder<B>> extends AbstractHttpConfigurer<T,B>
{
//创建认证过滤器
private DemoAuthFilter authFilter = new DemoAuthFilter();
//将过滤器加入 http 过滤处理责任链
@Override
public void configure(B http) throws Exception
{
//获取 Spring Security 共享的 AuthenticationManager 认证管理者实例
//将其设置到认证过滤器
authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
DemoAuthFilter filter = postProcess(authFilter);
//将过滤器加入 http 过滤处理责任链
http.addFilterBefore(filter, LogoutFilter.class);
}
}
  • DemoWebSecurityConfig

定义一个 Spring Security 安全配置类(WebSecurityConfigurerAdapter 子类), 对 Web 容器的 HTTP 安全认证机制进行配置。有两项工作:

  • 应用DemoAuthConfigurer 配置类;

  • 构造 AuthenticationManagerBuilder 认证管理者实例。

@EnableWebSecurity
public class DemoWebSecurityConfig extends WebSecurityConfigurerAdapter
{
//配置 HTTP 请求的安全策略,应用 DemoAuthConfigurer 配置类实例
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable()
...
.and()
//应用 DemoAuthConfigurer 配置类
.apply(new DemoAuthConfigurer<>())
.and()
.sessionManagement().disable();
}
//配置认证 Builder,由其负责构造 AuthenticationManager 认证管理者实例
//Builder 将构造 AuthenticationManager 实例,并且作为 HTTP 请求的共享对象存储
//在代码中可以通过 http.getSharedObject(AuthenticationManager.class) 来获取管理者实例
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
//加入自定义的 Provider 认证提供者实例
auth.authenticationProvider(demoAuthProvider());
}
//自定义的认证提供者实例
@Bean("demoAuthProvider" )
protected DemoAuthProvider demoAuthProvider()
{
return new DemoAuthProvider();
}
}
  • 执行测试

通过swagger-ui界面直接访问返回403,表示认证失败

图片

输入用户名,密码(zhangsan,123456),认证成功

图片

4.2 基于数据源认证流程实战

4.2.1 常见内置类

生产场景中,用户信息都存储在某个数据源(如数据库) 中,认证过程中涉及从数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值