<返回更多

OAuth网络安全登录认证(二)

2021-05-07  今日头条  开发架构狮rain
加入收藏

本文开始讲springsecurity框架登录认证授权的一些知识点。为什么没有说shiro这个框架,主要是现在大部分的主流项目中,特别是前后端分离的项目中权限框架一般都用的是springsecurity,比较适合,然后还有一点,就是一些开源框架(比如最新版本的工作流引擎activiti7)跟springsecurity的整合,促使我对这个springsecurity进一步加深了解。还有一个前提,就是本文是完全基于前后端分离的基础上写作,未分离项目可以做借鉴。

按照正常的思维,一个权限框架要解决的问题是:登录以及还有登录之后的访问。这个需要怎么实现,其实就是一串过滤器跟拦截器。用户没有登录,进行拦截;用户登录之后,带着证书登陆,拦截器先判断是否有证书,然后再判断证书是否合法,有一个不满足,都进行拦截。springsecurity这个框架其实本身封装的就是一连串的过滤器跟拦截器。这里借鉴一下网上的一张原理图片:

OAuth网络安全登录认证(二)

 

 

首先,我们先说登录。官方术语叫认证Authentication。主要是通过AuthenticationManager接口进行认证。(本文主要将通过用户名密码进行认证,其他认证方式后续文章会有说明。)AuthenticationManager的默认实现是ProviderManager,它又委托AuthenticationProvider实例来实现认证,我们通常用到的认证方式就是通过DaoAuthenticationProvider来认证的。(上边这几句话有点难以理解,实在不理解的话直接跳过,总之就是通过下边这个接口进行认证的,然后登录接口调用这个接口进行认证。)

public interface AuthenticationManager {
  Authentication authenticate(Authentication authentication)throws AuthenticationException;
}
// 用户登录认证
Authentication authentication = null;
try {
  // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername(这个实现类后边会有说明,这里主要讲登陆逻辑)
  authentication = authenticationManager
    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
}catch (Exception e){
  if (e instanceof BadCredentialsException) {
    //抛出登录异常
    throw new UserPasswordNotMatchException();
  }else{
    //抛出自定义异常
    throw new CustomException(e.getMessage());
  }
}

接下来,我们要把这套登录整合到我们的系统,需要用到我们自己的用户角色权限表。springsecurity中默认使用UserDetailsService来获取用户权限信息,我们需要自己实现这个接口,然后注入到认证接口中。

public class UserDetailsServiceImpl implements UserDetailsService{
    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
    @Autowired
    private ISysUserService userService;
    @Autowired
    private SysPermissionService permissionService;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
        SysUser user = userService.selectUserByUserName(username);//查询用户信息
        if (StringUtils.isNull(user)){
            throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
        }else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
            throw new BaseException("对不起,您的账号:" + username + " 已被删除");
        }else if (UserStatus.DISABLE.getCode().equals(user.getStatus())){
            throw new BaseException("对不起,您的账号:" + username + " 已停用");
        }
      	//讲用户信息跟权限信息统一封装到UserDetails
        new UserDetails(user, permissionService.getMenuPermission(user));
    }
}
public class SecurityConfig extends WebSecurityConfigurerAdapter{
   /**
     * 身份认证接口
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
      	//注入身份认证接口,通过bCryptPasswordEncoder密码加密认证
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

其次,登录认证都说完了,我们开始说过滤拦截,通过继承自
WebSecurityConfigurerAdapter,对Spring Security自定义配置添加过滤器,下边我们直接在代码里做注释说明:

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    @Autowired
    private UserDetailsService userDetailsService;//自定义用户认证逻辑
    @Autowired
    private AuthenticationEntryPointImpl unauthorizedHandler;//认证失败处理类
    @Autowired
    private LogoutSuccessHandlerImpl logoutSuccessHandler;//退出处理类
    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;//token认证过滤器
    @Autowired
    private CorsFilter corsFilter;//跨域过滤器
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        httpSecurity
                // CSRF禁用,因为不使用session
                .csrf().disable()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                // 对于登录login 验证码captchaImage 允许匿名访问
                .antMatchers("/login", "/captchaImage").anonymous()
                .antMatchers(HttpMethod.GET,"/*.html","/**/*.html", "/**/*.css", "/**/*.js").permitAll()
                .antMatchers("/processDefinition/**").permitAll()
                .antMatchers("/activitiHistory/**").permitAll()
                .antMatchers("/profile/**").anonymous()
                .antMatchers("/common/download**").anonymous()
                .antMatchers("/common/download/resource**").anonymous()
                .antMatchers("/swagger-ui.html").anonymous()
                .antMatchers("/swagger-resources/**").anonymous()
                .antMatchers("/webjars/**").anonymous()
                .antMatchers("/*/api-docs").anonymous()
                .antMatchers("/druid/**").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and().headers().frameOptions().disable();
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
        // 添加JWT filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加CORS filter
        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
        httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
    }
}

最后,我们对
JwtAuthenticationTokenFilter过滤器做主要说明,因为这是用户登录之后,每次访问接口的时候,都需要通过这个接口进行token验证,并把用户信息放入到SecurityContextHolder上下文中,然后后台服务就可以直接在上下文中获取用户信息。

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter{
    @Autowired
    private TokenService tokenService;
  
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
       //从请求头中获取token,并从缓存中查询用户信息(缓存中用户信息是在用户登录后放入缓存,可以加快查询效率)       
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())){
            tokenService.verifyToken(loginUser);//验证token,同时自动刷新token使用时间
          	//以下逻辑就是把通过token验证的用户信息放入到上下文中,后台服务可以直接通过上下文获取当前用户
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        chain.doFilter(request, response);
    }
}
//后台服务获取当前用户代码
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
	String username = ((UserDetails)principal).getUsername();
} else {
	String username = principal.toString();
}

以上,就是我对springsecurity安全框架做出的总结。另外,文章里的部分代码,是借鉴若依大佬的RuoyiVue这套前后端分离框架的,完全开源的,有不对的地方,请大家指正。后边文章,我会写前端如何跟后端进行token接口交互的文章,整合前端。

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>