不用过滤器(使用json传参方式登录)

SecurityConfig配置

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * spring security配置
 */
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /** 认证失败 处理类 */
    @Autowired
    private AuthenticationEntryPointImpl unauthorizedHandler;
    /** 退出 处理类 */
    @Autowired
    private LogoutSuccessHandlerImpl logoutSuccessHandler;
    /** jwt token认证过滤器 */
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    /** 手机验证码 认证器 */
    @Autowired
    private CaptchaAuthenticationProvider captchaAuthenticationProvider;
    /** 账号密码 认证器*/
    @Autowired
    private UserNameAuthenticationProvider userNameAuthenticationProvider;

    /**
     * 认证管理 AuthenticationManager
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable()
                .cors()
                .and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
                .and()
                // 过滤请求
                .authorizeRequests()
                // 允许匿名访问
                .antMatchers(antMatchersAnonymous()).anonymous()
                .antMatchers(HttpMethod.GET, antMatchersPermitAll()).permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();

        // 退出处理
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
        // 添加JWT filter(必须配置与UsernamePasswordAuthenticationFilter之前)
        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    // 允许匿名访问url
    private String[] antMatchersAnonymous() {
        return new String[]{"/login"  };
    }

    // 不需要任何限制url(静态资源)
    private String[] antMatchersPermitAll() {
        return new String[]{"/*.html"};
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 添加认证器
        // 新增短信验证码验证
        auth.authenticationProvider(captchaAuthenticationProvider);
        // 账号密码登录验证
        auth.authenticationProvider(userNameAuthenticationProvider);
    }

    /**
     * 密码加密规则 强散列哈希加密实现(密码加密算法)
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

账号密码

UserNameAuthenticationProvider 账号密码认证器

import java.util.Collections;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * 用户名密码认证器
 */
@Slf4j
@Component
public class UserNameAuthenticationProvider implements AuthenticationProvider {
	/** 进行账号认证实现 */
    @Autowired
    private UserDetailsService userDetailsService;
    /** 密码加密规则 */
    @Autowired
    private PasswordEncoder bCryptPasswordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        long time = System.currentTimeMillis();
        log.info("用户名/密码 开始登录验证 time:{}", time);
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
        // 1、去调用自己实现的UserDetailsService,返回UserDetails
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        // 2、密码进行检查,这里调用了PasswordEncoder,检查 UserDetails 是否可用。
        if (Objects.isNull(userDetails) || !bCryptPasswordEncoder.matches(password, userDetails.getPassword())) {
            throw new BadCredentialsException("账号或密码错误");
        }
        // 3、返回经过认证的Authentication
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userDetails, null, Collections.emptyList());
        result.setDetails(authentication.getDetails());
        log.info("用户名/密码 登录验证完成 time:{}, existTime:{}", time, (System.currentTimeMillis() - time));
        return result;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        boolean res = UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
        log.info("用户名/密码 是否进行登录验证 res:{}", res);
        return res;
    }
}

手机号验证码

CaptchaAuthenticationToken 短信验证码认证凭证

import java.util.Collection;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;

/**
 * 短信验证码认证凭证
 */
public class CaptchaAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    private final Object principal; // 手机号
    private String captcha;         // 验证码

    /**
     * 此构造函数用来初始化未授信凭据.
     *
     * @param principal
     * @param captcha
     */
    public CaptchaAuthenticationToken(Object principal, String captcha) {
        super(null);
        this.principal = principal;
        this.captcha = captcha;
        setAuthenticated(false);
    }

    /**
     * 此构造函数用来初始化授信凭据.
     *
     * @param principal
     * @param captcha
     * @param authorities
     */
    public CaptchaAuthenticationToken(Object principal, String captcha, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.captcha = captcha;
        super.setAuthenticated(true);
    }

    public Object getCredentials() {
        return this.captcha;
    }

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

    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");
        }
        super.setAuthenticated(false);
    }

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

CaptchaAuthenticationProvider 短信验证码认证器

import java.util.Collections;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;

/**
 * 短信验证码认证器
 */
@Slf4j
@Component
public class CaptchaAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserDetailsService userDetailsService;
    /** 验证码验证服务 */
    @Autowired
    private ICaptchaService captchaService;

    /**
     * 进行验证码认证
     *
     * @param authentication
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        long time = System.currentTimeMillis();
        log.info("手机验证码 开始登录验证 time:{}", time);

        String phone = authentication.getName();
        String rawCode = authentication.getCredentials().toString();
        // 1. 手机验证码验证
        captchaService.validate("admin:account:login:key:", phone, rawCode);
        // 1.根据手机号获取用户信息
        UserDetails userDetails = userDetailsService.loadUserByUsername(phone);
        if (Objects.isNull(userDetails)) {
            throw new BadCredentialsException("当前手机号不存在");
        }
        // 3、返回经过认证的Authentication
        CaptchaAuthenticationToken result = new CaptchaAuthenticationToken(userDetails, null, Collections.emptyList());
        result.setDetails(authentication.getDetails());
        log.info("手机验证码 登录验证完成 time:{}, existTime:{}", time, (System.currentTimeMillis() - time));
        return result;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        boolean res = CaptchaAuthenticationToken.class.isAssignableFrom(authentication);
        log.info("手机验证码 是否进行登录验证 res:{}", res);
        return res;
    }
}

ICaptchaService

/**
 * 验证码
 */
public interface ICaptchaService {

    /**
     * 缓存验证码
     *
     * @param key 缓存前缀key
     * @param phone
     * @param code
     */
    public void setCaptcha(String key, String phone, String code);

    /**
     * 验证验证码
     *
     * @param key 缓存前缀key
     * @param phone
     * @param code 
     * CustomException 自定义异常
     */
    public boolean validate(String key, String phone, String code) throws CustomException;

}

CaptchaServiceImpl 实现类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 验证码
 */
@Component
public class CaptchaServiceImpl implements ICaptchaService {

    @Autowired
    private RedisUtil redisUtil;

    @Override
    public void setCaptcha(String key, String phone, String code) {
        // 有效期5分钟
        redisUtil.set(key + phone, code, 300);
    }

    @Override
    public boolean validate(String key, String phone, String code) throws CustomException {
        if (StrUtils.isBlank(phone) || StrUtils.isBlank(code)) {
            throw new CustomException("验证验证码参数为空", 300);
        }
        // 获取缓存数据
        Object v = redisUtil.get(key + phone);
        if (null == v) {
            throw new CustomException("验证码过期,请重新获取", 300);
        }
        // 验证是否正确
        if (!code.equalsIgnoreCase(String.valueOf(v))) {
            throw new CustomException("验证码错误", 300);
        }
        // 验证通过 清除缓存
        redisUtil.delete(key + phone);
        return true;
    }
}

使用

下面为简版登录 具体写法以公司为准

@Slf4j
@RestController
public class LoginController {
    /**
     * 用户名密码 登录 
     * LoginBody 实体字段 (username)用户名, (password)用户密码/验证码, (type)登录类型 1-密码登录, 2-短信验证码登录 默认1
     */
    @PostMapping("/login")
    public Boolean login(@RequestBody LoginBody loginBody) {
        loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getType());
        return true;
    }
}

LoginService

import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

/**
 * 登录校验方法
 *
 */
@Slf4j
@Component
public class SysLoginService {

    @Resource
    private AuthenticationManager authenticationManager;


    /**
     * 登录验证
     *
     * @param username 用户名
     * @param password 密码
     * @param type     登录类型 1-密码登录, 2-短信验证码登录 默认1
     * @return 结果
     */
    public String login(String username, String password, Integer type) {
        // 用户验证
        Authentication authentication = null;
        try {
            if (null == type || type == 1) {
                // 密码登录
                authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
            } else {
                // 短信验证码登录
                authentication = authenticationManager.authenticate(new CaptchaAuthenticationToken(username, password));
            }
        } catch (Exception e) {
            log.error("登录验证异常", e);
            // 登录异常 自己处理业务逻辑
        }
        return null;
    }
}

使用过滤器实现(使用form表单传参方式登录)

推荐看这篇文章 https://www.felord.cn/captchaAuthenticationFilter.html

主要是自己实现过滤器 过滤器继承AbstractAuthenticationProcessingFilter

Logo

Authing 是一款以开发者为中心的全场景身份云产品,集成了所有主流身份认证协议,为企业和开发者提供完善安全的用户认证和访问管理服务

更多推荐