龙空技术网

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

清新阳光rain 298

前言:

而今你们对“过滤器验证用户登录怎么弄”可能比较重视,你们都想要学习一些“过滤器验证用户登录怎么弄”的相关知识。那么小编在网络上搜集了一些关于“过滤器验证用户登录怎么弄””的相关知识,希望各位老铁们能喜欢,我们一起来学习一下吧!

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

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

首先,我们先说登录。官方术语叫认证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上下文中,然后后台服务就可以直接在上下文中获取用户信息。

@Componentpublic 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接口交互的文章,整合前端。

标签: #过滤器验证用户登录怎么弄