springboot + spring security验证token进行用户认证

核心组件

SecurityContextHolder

SecurityContextHolder是spring security最基本的组件。用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限等这些都被保存在SecurityContextHolder中。SecurityContextHolder默认是使用ThreadLocal实现的(ThreadLocal相关原理分析),这样就保证了本线程内所有的方法都可以获得SecurityContext对象。

可以通此方法过来获取当前操作用户信息:

SecurityContextHolder.getContext().getAuthentication().getPrincipal();

默认返回的对象是UserDetails实例,其中包含了username,password和权限等信息,当然,我们也可以通过实现这个接口自定义我们自己的UserDetails实例,给我们自己的应用使用,以符合需要的业务逻辑。比如下面只对token进行操作就可以吧token作为属性放入UserDetails实现类中。

Authentication

Authentication是Spring Security方式的认证主体。

<1> Authentication是spring security包中的接口,直接继承自Principal类,而Principal是位于java.security包中的。可以见得,Authentication在spring security中是最高级别的身份/认证的抽象。
<2> 由这个顶级接口,我们可以得到用户拥有的权限信息列表,密码,用户细节信息,用户身份信息,认证信息。
authentication.getPrincipal()返回了一个Object,我们将Principal强转成了Spring Security中最常用的UserDetails,这在Spring Security中非常常见,接口返回Object,使用instanceof判断类型,强转成对应的具体实现类。接口详细解读如下:

  • getAuthorities(),权限信息列表,默认是GrantedAuthority接口的一些实现类,通常是代表权限信息的一系列字符串。
  • getCredentials(),密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
  • getDetails(),细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。
  • getPrincipal(),最重要的身份信息,大部分情况下返回的是UserDetails接口的实现类,也是框架中的常用接口之一。

AuthenticationManager

AuthenticationManager(接口)是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中身份认证的方式有多种,一般不使用AuthenticationManager,而是使用AuthenticationManager的实现类ProviderManager ,ProviderManager内部会维护一个List<AuthenticationProvider>列表,存放多种认证方式,实际上这是委托者模式的应用(Delegate)。也就是说,核心的认证入口始终只有一个:AuthenticationManager,不同的认证方式对应不同的AuthenticationProvider。

总结:

SecurityContextHolder:存放身份信息的容器

Authentication:用户信息的抽象

AuthenticationManager:身份认证器

认证流程

1、通过过滤器过滤到用户请求的接口,获取身份信息(假如有多个认证方式会配置provider的顺序)

2、一般将身份信息封装到封装成Authentication下的实现类UsernamePasswordAuthenticationToken中

3、通过AuthenticationManager 身份管理器(通过配置找到对应的provider)负责验证这个UsernamePasswordAuthenticationToken

4、认证成功后(认证逻辑一般在service中),AuthenticationManager身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。

5、SecurityContextHolder安全上下文容器将第2步填充了信息的UsernamePasswordAuthenticationToken,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中来建立安全上下文(security context)。

结合springboot实现对token验证

1、场景

拦截api/的所有接口进行验证,验证token用户与id用户是否一致,不一致或token过期则没有权限访问

2、实现

1、添加security相关依赖:spring-boot-starter-security   spring-security-oauth2

2、全局配置类,根据不同需求配置不同的过滤器和provider(代码片段)

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {


    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SecurityOrgPeopleMapper securityOrgPeopleMapper;

    @Autowired
    private ImCheckTokenFactory imCheckTokenFactory;


    // oauth2 server
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        addProvider(auth);
    }

//指定provider
    private void addProvider(AuthenticationManagerBuilder auth) {
            auth.authenticationProvider(imAuthenticationProvider());  
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 请求过滤 对api/对所有接口都验证
        http
                .authorizeRequests()
                    .antMatchers("/api/**").access("@permissionChecker.hasPermission(authentication,request)")
                .anyRequest().authenticated();

        registerFilter(http);
    }

//指定filter过滤器
    private void registerFilter(HttpSecurity http) throws Exception {
                http
                        .addFilterBefore(new ImAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class); 
    }


   private ImAuthenticationProvider imAuthenticationProvider() {
        return new ImAuthenticationProvider(imCheckTokenFactory, securityOrgPeopleMapper);
    }

    
}

过滤器:

public class ImAuthenticationFilter extends GenericFilterBean {

    private AuthenticationManager authenticationManager;

    public ImAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        // 其他过滤器已经认证通过了
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.isAuthenticated()) {
            chain.doFilter(request, response);
            return;
        }
        HttpServletRequest httpRequest = asHttp(request);
        HttpServletResponse httpResponse = asHttp(response);

//获取接口中都用户信息
        String userId = obtainUserId(httpRequest);
        String token = obtainToken(httpRequest);
        String client = obtainClient(httpRequest);

        try {
            checkToken(token);
            imProcessTokenAuthentication(Integer.parseInt(userId), token, client);
            chain.doFilter(request, response);
        } catch (UserAuthenticationException userAuthenticationException) {
            logger.warn(userAuthenticationException.getMessage());
            httpResponse.setStatus(userAuthenticationException.getStatus());
        } catch (AuthenticationException authenticationException) {
            chain.doFilter(request, response);
        }

    }

    private String obtainToken(HttpServletRequest request) {
        String tokenParameter = "F-Session";
        String token = request.getHeader(tokenParameter);
        if (Objects.isNull(token)) {
            token = request.getParameter(tokenParameter);
        }
        return token;
    }

    private String obtainUserId(HttpServletRequest request) {
        String userIdParameter = "userId";
        return request.getParameter(userIdParameter);
    }

    private String obtainClient(HttpServletRequest request) {
        String clientParameter = "client";
        return request.getParameter(clientParameter);
    }

    private HttpServletRequest asHttp(ServletRequest request) {
        return (HttpServletRequest) request;
    }

    private HttpServletResponse asHttp(ServletResponse response) {
        return (HttpServletResponse) response;
    }

    private void checkToken(String token) {
        if (StringUtils.isEmpty(token)) {
            throw new UserAuthenticationException(SecurityHttpServletResponse.TOKEN_INVALID, "authenticate.fail");
        }
    }

    //im//将用户信息封装到ImTokenAuthentication(自定义用户信息类)中
    private void imProcessTokenAuthentication(Integer userId, String token, String client) {

        Authentication resultOfAuthentication = imTryToAuthenticateWithToken(userId, token, client);
        SecurityContextHolder.getContext().setAuthentication(resultOfAuthentication);
    }

    private Authentication imTryToAuthenticateWithToken(Integer userId, String token, String client) {
        ImTokenAuthentication imTokenAuthentication = new ImTokenAuthentication(userId, token, client);
        return tryToAuthenticate(imTokenAuthentication);
    }


    private Authentication tryToAuthenticate(Authentication requestAuthentication) throws AuthenticationException {
//找到配置的authenticationManager实现类provider进行验证返回充满信息的Authentication
        Authentication responseAuthentication = authenticationManager.authenticate(requestAuthentication);
        if (responseAuthentication == null || !responseAuthentication.isAuthenticated()) {
            throw new InternalAuthenticationServiceException("Unable to authenticate for provided credentials");
        }
        logger.debug("User successfully authenticated");
        return responseAuthentication;
    }
}

自定义authentication(一般继承UsernamePasswordAuthenticationToken,此项目是在前任的项目基础上写的)

public class ImTokenAuthentication extends TokenAuthenticationToken {

    private Integer userId;
    private String client;

    public ImTokenAuthentication(Integer userId, String token, String client) {
        super(token);
        this.userId = userId;
        this.client = client;
    }

    public ImTokenAuthentication(Integer userId, String token, String client, SecurityUserDetails details) {
        super(token);
        this.userId = userId;
        this.client = client;
        setDetails(details);
    }

    public Integer getUserId() {
        return userId;
    }

    public String getClient() {
        return client;
    }
}

provider

public class ImAuthenticationProvider implements AuthenticationProvider {

    private SecurityOrgPeopleMapper securityOrgPeopleMapper;//根据项目需求注入

    private ImCheckTokenFactory imCheckTokenFactory;//根据项目需求注入

    public ImAuthenticationProvider(ImCheckTokenFactory imCheckTokenFactory, SecurityOrgPeopleMapper securityOrgPeopleMapper) {
        this.securityOrgPeopleMapper = securityOrgPeopleMapper;
        this.imCheckTokenFactory = imCheckTokenFactory;
    }


    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//自定义的装载用户信息的类
        ImTokenAuthentication imTokenAuthentication = (ImTokenAuthentication) authentication;
//获取在过滤器中放入authentication的用户信息
        String token = authentication.getPrincipal().toString();
        Integer userId = Integer.parseInt(imTokenAuthentication.getUserId().toString());
        String client = imTokenAuthentication.getClient();

//获取验证token所在的sevice
        ImCheckTokenService imCheckTokenService = imCheckTokenFactory.getService(client);

        if (Objects.isNull(imCheckTokenService)) {
            authentication.setAuthenticated(false);
            throw new UserAuthenticationException(SecurityHttpServletResponse.TOKEN_INVALID, "authenticate.fail");
        }
//验证token逻辑
        Object object = imCheckTokenService.checkToken(userId, token);
        if (Objects.isNull(object)) {
            throw new BadCredentialsException("");
        }

        OrgPeople orgPeople = securityOrgPeopleMapper.getPeopleBySystemUserId(userId);
     
        imTokenAuthentication.setDetails(new SecurityUserDetails((Account) ;
//在servcice中验证不通过就已经抛出异常了,此处正常运行则设置验证通过
        authentication.setAuthenticated(true);
        return authentication;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return (ImTokenAuthentication.class.isAssignableFrom(authentication));
    }
}

参考:

spring security架构

https://www.cnblogs.com/shiyu404/p/6530894.html

SpringSecurity核心组件_securitycontextholder.getcontext()_rw-just-go-forward的博客-CSDN博客

官方文档

  • 33
    点赞
  • 135
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孟林洁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值