前后端分离SpringSecurity授权、认证功能、原理介绍

211 阅读1分钟

本文章主要针对以下四个方面进行讲解,包括认证与授权

项目目录结构如下:

SpringSecurity配置部分 (重点)

protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests() // 权限配置
    .antMatchers(
    ).permitAll()//配置免拦截接口
    .anyRequest().authenticated() // 其他的需要登陆后才能访问
    .and().httpBasic().authenticationEntryPoint(userNotLoginHandler) // 配置未登录处理类
    .and().formLogin().loginProcessingUrl("/api/login")// 配置登录URL
    .successHandler(userLoginSuccessHandler) // 配置登录成功处理类
    .failureHandler(userLoginFailureHandler) // 配置登录失败处理类
    .and().logout().logoutUrl("/api/logout")// 配置登出地址
    .logoutSuccessHandler(userLogoutSuccessHandler) // 配置用户登出处理类
    .and().exceptionHandling().accessDeniedHandler(userAccessDeniedHandler)// 配置没有权限处理类
    .and().cors()// 开启跨域
    .and().csrf().disable(); // 禁用跨站请求伪造防护
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 禁用session(使用Token认证)
    http.headers().cacheControl(); // 禁用缓存
    http.addFilter(new JWTAuthenticationFilter(authenticationManager())); //// 添加JWT过滤器

}
  1. 忽略不需要认证与授权的Url
for (String url : ignoreUrlsConfig.getUrls()) {
    registry.antMatchers(url).permitAll();
}
  1. 登入认证部分(重点)

UsernamePasswordFilter: 过滤器类,主要认证用户名和密码。

Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。

AuthenticationManager接口:定义了认证Authentication的方法

UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。

UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

成功处理器 AuthenticationSuccessHandler实现:

public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) {
        SysUserDetails sysUserDetails = (SysUserDetails) authentication.getPrincipal();
        // 获得请求IP
        String ip = AccessAddressUtils.getIpAddress(request);
        sysUserDetails.setIp(ip);
        String token = JWTTokenUtils.createAccessToken(sysUserDetails);

        // 保存Token信息到Redis中
        JWTTokenUtils.setTokenInfo(token, sysUserDetails.getUsername(), ip);

        log.info("用户{}登录成功,Token信息已保存到Redis", sysUserDetails.getUsername());

        Map<String, String> tokenMap = new HashMap<>();
        tokenMap.put("token", token);
        tokenMap.put("openId", sysUserDetails.getUsername());
        ResponseUtils.responseJson(response, ResponseUtils.response(200, "登录成功", tokenMap));
    }
}

失败处理器 AuthenticationFailureHandler实现:

public class UserLoginFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) {
        ResponseUtils.responseJson(response, ResponseUtils.response(500, "登录失败", exception.getMessage()));
    }
}
  1. 注销Url部分
public class UserLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
                                Authentication authentication) {
        // 添加到黑名单
        String token = request.getHeader(JWTConfig.tokenHeader);
        JWTTokenUtils.addBlackList(token);

        log.info("用户{}登出成功,Token信息已保存到Redis的黑名单中", JWTTokenUtils.getUserNameByToken(token));

        SecurityContextHolder.clearContext();
        ResponseUtils.responseJson(response, ResponseUtils.response(200, "登出成功", null));
    }
}
  1. 授权部分

在ExceptionTranslationFilter类中,根据异常信息,识别出是未登入出现异常,还是无权限出现异常,源码逻辑如下:

private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException {
    if (exception instanceof AuthenticationException) {
        this.logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
        this.sendStartAuthentication(request, response, chain, (AuthenticationException)exception);
    } else if (exception instanceof AccessDeniedException) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (!this.authenticationTrustResolver.isAnonymous(authentication) && !this.authenticationTrustResolver.isRememberMe(authentication)) {
            this.logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception);
            this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);
        } else {
            this.logger.debug("Access is denied (user is " + (this.authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point", exception);
            this.sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication", "Full authentication is required to access this resource")));
        }
    }

}

我们可以利用自己实现的处理器,如未登入处理器(AuthenticationEntryPoint)和 权限不足处理器(AccessDeniedHandler),实现代码如下:

// 未登入处理器
public class UserNotLoginHandler implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        ResponseUtils.responseJson(response, ResponseUtils.response(401, "未登录", authException.getMessage()));
    }
}
// 权限不足处理器
public class UserAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException accessDeniedException) throws IOException, ServletException {
        ResponseUtils.responseJson(response, ResponseUtils.response(403, "拒绝访问", accessDeniedException.getMessage()));
    }

}

可以实现PermissionEvaluator接口类,来验证某个用户是否有访问Url的权限

public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
    SysUserDetails sysUserDetails = (SysUserDetails) authentication.getPrincipal();
    // 用户权限
    Set<String> permissions = new HashSet<String>();

    List<SysUser> list = sysUserService.findRoleByOpenId(sysUserDetails.getOpenid());
    list.forEach(auth -> {
        permissions.add(String.valueOf(auth.getRole()));
    });

    // 判断是否拥有权限
    if (permissions.contains(permission.toString())) {
        return true;
    }
    return false;
}