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 小时前
Java开发中的@EnableWebMvc注解和WebMvcConfigurer接口
java·开发语言·spring boot·spring·tomcat·maven·idea
JavaGuide6 小时前
推荐一个基于 Spring Boot 4.0 + Java 21 + Spring AI 2.0 的大模型项目!
java·spring boot·spring
NE_STOP7 小时前
spring6-多种类型的注入方式
spring
小马爱打代码7 小时前
Spring Boot :使用 Spring Cache 注解方式集成 Redis
spring boot·redis·spring
有味道的男人8 小时前
1688获得商品类目调取商品榜单
java·前端·spring
树码小子8 小时前
SpringMCV(9)响应:返回静态页面 & 修改响应数据
spring·mvc
Remember_9939 小时前
Spring 事务深度解析:实现方式、隔离级别与传播机制全攻略
java·开发语言·数据库·后端·spring·leetcode·oracle
roman_日积跬步-终至千里9 小时前
【Java并发】用 JMM 与 Happens-Before 解决多线程可见性与有序性问题
java·开发语言·spring
独断万古他化9 小时前
【Spring 核心:AOP】基础到深入:思想、实现方式、切点表达式与自定义注解全梳理
java·spring·spring aop·aop·切面编程
树码小子10 小时前
SpringMVC(10)综合案例练习:计算器,登录
spring·mvc