Spring Security自定义认证器

在了解过Security的认证器后,如果想自定义登陆,只要实现AuthenticationProvider还有对应的Authentication就可以了

Authentication

首先要创建一个自定义的Authentication,Security提供了一个Authentication的子类AbstractAuthenticationToken

我们实现这个类可以了,他已经实现了Authentication的一些方法

java 复制代码
public class NamePassAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = 520L;
    private final Object principal;
    private Object credentials;

//提供第一次进来的构造方法
    public NamePassAuthenticationToken(Object principal, Object credentials) {
        super((Collection)null);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(false);
    }

//提供填充Authentication的构造方法
    public NamePassAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return this.credentials;
    }

    @Override
    public Object getPrincipal() {
        return this.principal;
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        } else {
            super.setAuthenticated(false);
        }
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
        this.credentials = null;
    }
}

这个类关键就是一个是认证的,一个没认证的的构造器

AuthenticationProvider

接着是AuthenticationProvider,需要实现他的authenticate方法

java 复制代码
@Setter
public class NamePassAuthenticationProvider implements AuthenticationProvider {

    private CustomUserDetailsService userDetailsService;

    private PasswordEncoder passwordEncoder;

    @Override
    //具体认证逻辑
    public Authentication authenticate(Authentication authentication) {
        NamePassAuthenticationToken authenticationToken = (NamePassAuthenticationToken) authentication;
        String username = (String) authenticationToken.getPrincipal();
        String password = (String) authenticationToken.getCredentials();
        //让具体认证类去认证
        UserDetails user = userDetailsService.loadUserByUsername(username); 
        boolean matches = passwordEncoder.matches(password, user.getPassword());
        if (!matches) {
            ResMsg.throwException(AuthExceptionGroup.AUTH_ERROR);
        }
        //填充Authentication
        NamePassAuthenticationToken authenticationResult = new NamePassAuthenticationToken(user, password, user.getAuthorities());
        authenticationResult.setDetails(authenticationToken.getDetails());
        return authenticationResult;
    }

    @Override
    //指定具体的Authentication
    //根据你指定的Authentication来找到具体的Provider
    public boolean supports(Class<?> authentication) {
        return NamePassAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

SecurityConfigurerAdapter

接着就是填充配置了

java 复制代码
@Component
public class NamePassAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public void configure(HttpSecurity http) {
        //phonePass provider
        NamePassAuthenticationProvider provider = new NamePassAuthenticationProvider();
        provider.setUserDetailsService(customUserDetailsService);
        provider.setPasswordEncoder(passwordEncoder);
        http.authenticationProvider(provider);
    }
}

接下来就是导入配置了

通常都会有一个实现了WebSecurityConfigurerAdapter的配置类

把配置类注入进来

java 复制代码
@Autowired
private NamePassAuthenticationSecurityConfig namePassAuthenticationSecurityConfig; 
    
protected void configure(HttpSecurity http) throws Exception {
  http.apply(namePassAuthenticationSecurityConfig);
}

UserDetailsService

UserDetailsService是具体的认证实现类

这个类就非常熟悉了,只需要实现他的loadUserByUsername方法,就可以实现认证了

java 复制代码
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        AuthmsViewAccount account = accountService.getAccount(username);
        if(account == null) {
            ResMsg.throwException(AUTH_ERROR);
        }

        if (account.getStatus() != 1) {
            ResMsg.throwException(ACCOUNT_HAS_BANED);
        }

        String spliceStaffInfo = String.format("%d-%s",account.getAccountId(),account.getUsername());
//只要Collection<? extends GrantedAuthority> authorities
//这个参数不为空,就表明认证通过,所以空集合也可以通过
        return new User(spliceStaffInfo,account.getPassword(), AuthorityUtils.NO_AUTHORITIES);
    }

把认证结果填充到上下文中

TokenFilter

如果结合了Token,那么需要从token中识别该用户

java 复制代码
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {

    String bearerToken = resolveToken(request);

    if (bearerToken != null && !"".equals(bearerToken.trim()) && SecurityContextHolder.getContext().getAuthentication() == null) {
        //从redis中获取该用户
        NamePassAuthenticationToken namePassAuthenticationToken = authRedisHelper.get(bearerToken);
        if(namePassAuthenticationToken != null) {
            //将信息保存到上下文中
 SecurityContextHolder.getContext().setAuthentication(namePassAuthenticationToken);
        }
    }

    chain.doFilter(request, response);
}

private String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
            return bearerToken.substring(7);
        }
        return null;
    }
java 复制代码
public NamePassAuthenticationToken get(String bearerToken){
    String spliceStaffInfo = (String)redisRepository.get(formatKey(bearerToken));
    if(spliceStaffInfo == null) {
        return null;
    }
    return new NamePassAuthenticationToken(new AuthStaff(spliceStaffInfo),null,AuthorityUtils.NO_AUTHORITIES);
}

登录过程

在登录的时候,就需要用到这个自定义的认证器了

java 复制代码
// 通过用户名和密码创建一个 Authentication 认证对象,实现类为 NamePassAuthenticationToken
NamePassAuthenticationToken authenticationToken = new NamePassAuthenticationToken(user.getUsername(), user.getPassword());

//通过 AuthenticationManager(默认实现为ProviderManager)的authenticate方法验证 Authentication 对象 
//AuthenticationManager会通过你传入的authenticationToken来找到具体的Provider
Authentication authentication = authenticationManager.authenticate(authenticationToken);
//填充用户信息到secrity中的user里
User principal = (User) authentication.getPrincipal();
//获取认证后的信息
NamePassAuthenticationToken namePassAuthenticationToken = new NamePassAuthenticationToken(new AuthStaff(principal.getUsername()), null, authentication.getAuthorities());
// 生成token
String bearerToken = IdUtil.fastSimpleUUID();
// 加载到reids
authRedisHelper.set(bearerToken, namePassAuthenticationToken);

这样就实现了自定义的认证器了

相关推荐
雨雨雨雨雨别下啦1 小时前
Spring AOP概念
java·后端·spring
on the way 1231 小时前
day04-Spring之Bean的生命周期
java·后端·spring
paopaokaka_luck3 小时前
基于SpringBoot+Uniapp的自习室预约小程序(腾讯地图API、Echarts图形化分析、二维码识别)
vue.js·spring boot·后端·spring·echarts
小小8程序员4 小时前
Spring Boot AOP 全面解析(原理 + 实战 + 场景)
java·spring boot·spring
何中应4 小时前
【面试题-8】Spring/Spring MVC/Spring Boot/Spring Cloud
java·spring boot·后端·spring·mvc·面试题
没什么本事4 小时前
Springboot CGLIB 代理对象问题
java·spring boot·spring
Javatutouhouduan4 小时前
SpringBoot整合reids之JSON序列化文件夹操作
java·spring boot·spring·bootstrap·html·后端开发·java架构师
她说..4 小时前
Spring AOP场景5——异常处理(附带源码)
java·数据库·后端·spring·springboot·spring aop
醇氧4 小时前
springAI学习 (二) 模型
java·学习·spring·ai·ai编程
0和1的舞者4 小时前
《MyBatis 从入门到上手:超全基础操作 + XML 配置指南》
数据库·spring boot·学习·spring·mybatis·框架·开发