springSecurity 登录以及用户账号密码解析原理

栏目: 后端 · 发布时间: 5年前

内容简介:用户登录基本流程处理如下:1 SecurityContextPersistenceFilter

springSecurity 拦截器链

springSecurity 登录以及用户账号密码解析原理

用户登录基本流程处理如下:

1 SecurityContextPersistenceFilter

2 AbstractAuthenticationProcessingFilter

3 UsernamePasswordAuthenticationFilter

4 AuthenticationManager

5 AuthenticationProvider

6 userDetailsService

7 userDetails

8 认证通过

9 SecurityContext

10 SecurityContextHolder

11 AuthenticationSuccessHandler

用户页面登录

1 首先进入 SecurityContextPersistenceFilter 拦截器

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
  。。。。。省略
        //HttpRequestResponseHolder 对象
        HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
        // 判断session是否存在,如果不存在则新建一个session
        SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
        boolean var13 = false;

        try {
            var13 = true;
            //将securiryContext放入SecurityContextHolder中
            SecurityContextHolder.setContext(contextBeforeChainExecution);
            //调用下一个拦截器,也就是之后所有的拦截器
            chain.doFilter(holder.getRequest(), holder.getResponse());
            var13 = false;
        } finally {
            if (var13) {
                //获取从context
                SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
                //清空SecurityContextHolder 中的contex 临时保存
                SecurityContextHolder.clearContext();
                //保存后面过滤器生成的数据 到SecurityContextRepository中
                this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
                request.removeAttribute("__spring_security_scpf_applied");
                if (debug) {
                    this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
                }

            }
        }
        //从SecurityContextHolder获取SecurityContext实例  8
        SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
        //清空SecurityContextHolder中的SecurityContext
        SecurityContextHolder.clearContext();
        //将SecurityContext实例保存到session中,以便下次请求时候用    9
        this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
        request.removeAttribute("__spring_security_scpf_applied");
        if (debug) {
            this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
        }

    }
}

loadContext 判断session是否存在 没有新建一个

public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
    HttpServletRequest request = requestResponseHolder.getRequest();
    HttpServletResponse response = requestResponseHolder.getResponse();
    //如果session不存在则返回null
    HttpSession httpSession = request.getSession(false);
    //根据 private String springSecurityContextKey = "SPRING_SECURITY_CONTEXT"; 
    //获取原来的session
    SecurityContext context = this.readSecurityContextFromSession(httpSession);
    if (context == null) {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("No SecurityContext was available from the HttpSession: " + httpSession + ". A new one will be created.");
        }
        //如果session 为null  则新建一个
        context = this.generateNewContext();
    }
    //将session  保存到 内部类SaveToSessionResponseWrapper 中
    HttpSessionSecurityContextRepository.SaveToSessionResponseWrapper wrappedResponse = new HttpSessionSecurityContextRepository.SaveToSessionResponseWrapper(response, request, httpSession != null, context);
    //保存在 HttpRequestResponseHolder对象中
    requestResponseHolder.setResponse(wrappedResponse);
    if (this.isServlet3) {
        requestResponseHolder.setRequest(new HttpSessionSecurityContextRepository.Servlet3SaveToSessionRequestWrapper(request, wrappedResponse));
    }

    return context;
}

readSecurityContextFromSession 方法中 根据判断session是否存在 会根据 “ SPRING_SECURITY_CONTEXT ”

获取session

用户登录即是认证

登录的实现方式:

2 进入AbstractAuthenticationProcessingFilter类

springSecurity 登录以及用户账号密码解析原理

3 UsernamePasswordAuthenticationFilter 实现了类 AbstractAuthenticationProcessingFilter 调用自身的attemptAuthentication方法

获取用户名和密码,构建token

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    if (this.postOnly && !request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    } else {
        String username = this.obtainUsername(request);
        String password = this.obtainPassword(request);
        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();
        //构建token ,此时没有进行验证
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        this.setDetails(request, authRequest);
        // AuthenticationManager 将token传递给  的 authenticate方法 进行 token验证
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}
//获取密码
protected String obtainPassword(HttpServletRequest request) {
    return request.getParameter(this.passwordParameter);
}
//获取账号
protected String obtainUsername(HttpServletRequest request) {
    return request.getParameter(this.usernameParameter);
}

调用authenticate方法,进行token

4 AuthenticationManager 接口

1 AuthenticationManager 接口

public interface AuthenticationManager {
    Authentication authenticate(Authentication var1) throws AuthenticationException;
}

2 ProviderManager 实现了AuthenticationManager 接口

authenticate方法中
    result = provider.authenticate(authentication);  //返回成功的认证

重写authenticate方法 来获取用户验证信息

5 AuthenticationProvider 接口中方法

Authentication authenticate(Authentication authentication)
      throws AuthenticationException;

AbstractUserDetailsAuthenticationProvider 实现了AuthenticationProvider 接口

public Authentication authenticate(Authentication authentication) //authentication 传递过来token
      throws AuthenticationException {
   Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
         () -> messages.getMessage(
               "AbstractUserDetailsAuthenticationProvider.onlySupports",
               "Only UsernamePasswordAuthenticationToken is supported"));

   // Determine username    获取密码
   String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
         : authentication.getName();

   boolean cacheWasUsed = true;
   //从缓存中获取
   UserDetails user = this.userCache.getUserFromCache(username);
    //没有缓存
   if (user == null) {
      cacheWasUsed = false;

      try {
      //查询数据库  获取用户账号密码
         user = retrieveUser(username,
               (UsernamePasswordAuthenticationToken) authentication);
      }
      catch (UsernameNotFoundException notFound) {
         logger.debug("User '" + username + "' not found");

//此处 不能抛出异常 UsernameNotFoundException 
         if (hideUserNotFoundExceptions) {
            throw new BadCredentialsException(messages.getMessage(
                  "AbstractUserDetailsAuthenticationProvider.badCredentials",
                  "Bad credentials"));
         }
         else {
            throw notFound;
         }
      }

      Assert.notNull(user,
            "retrieveUser returned null - a violation of the interface contract");
   }

   try {
   //检查账号是否过期等操作
      preAuthenticationChecks.check(user);
      //
      additionalAuthenticationChecks(user,
            (UsernamePasswordAuthenticationToken) authentication);
   }
   catch (AuthenticationException exception) {
      if (cacheWasUsed) {
         // There was a problem, so try again after checking
         // we're using latest data (i.e. not from the cache)
         cacheWasUsed = false;
         user = retrieveUser(username,
               (UsernamePasswordAuthenticationToken) authentication);
         preAuthenticationChecks.check(user);
         additionalAuthenticationChecks(user,
               (UsernamePasswordAuthenticationToken) authentication);
      }
      else {
         throw exception;
      }
   }

   postAuthenticationChecks.check(user);

   if (!cacheWasUsed) {
      this.userCache.putUserInCache(user);
   }

   Object principalToReturn = user;

   if (forcePrincipalAsString) {
      principalToReturn = user.getUsername();
   }
    //创建成功的认证Authentication
   return createSuccessAuthentication(principalToReturn, authentication, user);
}

retrieveUser 方法查询用户数据:

调用自身的抽象方法

protected abstract UserDetails retrieveUser(String username,
      UsernamePasswordAuthenticationToken authentication)
      throws AuthenticationException;

DaoAuthenticationProvider 实现类

DaoAuthenticationProvider 继承 AbstractUserDetailsAuthenticationProvider 重写 retrieveUser 方法

用来根据用户名查询用户数据

protected final UserDetails retrieveUser(String username,
      UsernamePasswordAuthenticationToken authentication)
      throws AuthenticationException {
   prepareTimingAttackProtection();
   try {
     // 根据用户名查询数据
      UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
      if (loadedUser == null) {
         throw new InternalAuthenticationServiceException(
               "UserDetailsService returned null, which is an interface contract violation");
      }
      return loadedUser;
   }
   catch (UsernameNotFoundException ex) {
      mitigateAgainstTimingAttack(authentication);
      throw ex;
   }
   catch (InternalAuthenticationServiceException ex) {
      throw ex;
   }
   catch (Exception ex) {
      throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
   }
}

6 UserServiceDatils 接口 调用 loadUserByUsername 查询数据

7 userDetails对象返回数据

UserServiceDatils 接口需要自己实现(6.7一起进行)

public class MUserDetailsService implements UserDetailsService {

     Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private UsersMapper usersMapper;


    /**
     *@description:  需要从数据库中通过用户名来查询用户的信息和用户所属的角色
     *@author: wangl
     *@time:  2019/1/8 10:44
     *@version 1.0
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        logger.info("===========授权============");

        UserDetails userDeatils = null;

        //通过用户名 查询密码
        Users users = usersMapper.selectUserByUserName(username);

        Set<MGrantedAuthority> authorities = new HashSet<>();
        //查询用户角色
        if(null != users){
           logger.info("用户 = " + users.getUsername() + "----" + users.getPassword());
            //查询用户权限
            List<Users> usersList = usersMapper.selectRolesAndResourceByUserId(users.getId());
            if(null != usersList && usersList.size()>0){
                usersList.forEach(user->{
                     //存放role name  或者 权限名字
                    authorities.add(new MGrantedAuthority(user.getResourceName())); 
                });
            }else{
                System.out.println("用户无权限。。");

                throw new BadCredentialsException("not found ... ");
            }

7 返回数据
            userDeatils = new Users(users.getUsername(),users.getPassword(),authorities);

        }else{
        
            throw new BadCredentialsException("用户名不存在");
        }

        return userDeatils;
    }

查询用户数据之后

返回查询到的loadUser对象
然后用户账号是否被锁定,过期等验证
this.preAuthenticationChecks.check(user);

密码验证

protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    if (authentication.getCredentials() == null) {
        this.logger.debug("Authentication failed: no credentials provided");
        throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
    } else {
        //获取密码
        String presentedPassword = authentication.getCredentials().toString();
        //匹配密码
        if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            this.logger.debug("Authentication failed: password does not match stored value");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
    }
}

自定义密码验证器

@Slf4j
@Component
public class PasswordEncorder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        log.info("============ charSequence.toString()  ============== " + charSequence.toString());
        return MD5Util.encode(charSequence.toString());
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {

        String pwd = charSequence.toString();
        log.info("前端传过来密码为: " + pwd);
        log.info("加密后密码为: " + MD5Util.encode(charSequence.toString()));
        //s 应在数据库中加密
        if( MD5Util.encode(charSequence.toString()).equals(MD5Util.encode(s))){
            return true;
        }

        throw new DisabledException("--密码错误--");
    }
    
}

11 AuthenticationSuccessHandler

/**
 *@description: 自定义登陆成功处理类
 *@author: wangl
 *@time:  2019/1/14 17:46
 *@version 1.0
 */

@Component
public class MyAuthenctiationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        logger.info("登陆成功。。");
        String name = authentication.getName();
        HttpSession session = request.getSession();
        session.setAttribute("user",name);
        response.sendRedirect("/success");
        //super.onAuthenticationSuccess(request, response, authentication);
    }
}

配置类

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    MUserDetailsService mUserDetailsService; //userDetails 

    @Autowired
    MyFilterSecurityInterceptor myFilterSecurityInterceptor; //自定义拦截器

    @Autowired
    PasswordEncoder passwordEncoder; //密码验证器

    @Autowired
    private MyAuthenctiationFailureHandler myAuthenctiationFailureHandler;//失败处理
    @Autowired
    private MyAuthenctiationSuccessHandler myAuthenctiationSuccessHandler;//成功处理

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().mvcMatchers("/static/**"); //过滤静态资源
    }


    /**定义认证用户信息获取来源,密码校验规则等**/
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //从内存中获取
        //明文方式提交
         /*   auth.inMemoryAuthentication().withUser("zs").password("1234").roles("USER")
            .and().withUser("admin").password("1234").roles("ADMIN"); //从内存中获取
           */

        auth.userDetailsService(mUserDetailsService) .passwordEncoder(passwordEncoder);  //密码加密方式
        /*auth.authenticationProvider(customAuthenticationProvider)
                .authenticationProvider(authenticationProvider()) //增加
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());*/


        /*
            auth.inMemoryAuthentication().passwordEncoder(new PasswordEncorder()).withUser("zs").password("1234").roles("USER")
            .and().withUser("admin").password("1234").roles("ADMIN")
            ;*/
    }

    /**定义安全策略**/
    @Override
    protected void configure(HttpSecurity http) throws Exception {
      

        http.authorizeRequests() ////配置安全策略
                .antMatchers("/","/login.html","/loginPage").permitAll()   // 定义请求不需要验证
                //.antMatchers("/admin/next").hasRole("ADMIN")  // 设置只有管理员才能访问的url
                //.antMatchers("/admin/**").hasAnyRole("ADMIN")  // 设置多个角色访问的url
                .anyRequest().authenticated()  //其余所有请求都需要验证

                .and()

                .formLogin()  //配置登录页面

                .loginPage("/loginPage")  //登录页面访问路径
                .loginProcessingUrl("/login") //登录页面提交表单路径
                .successHandler(myAuthenctiationSuccessHandler)//成功页面
                .failureHandler(myAuthenctiationFailureHandler) //失败后跳转路径

                .and()
                .logout()   //登出不需要验证
                .logoutUrl("/logout").permitAll()

               // .and()

                //.authorizeRequests()
                //.antMatchers("/admin/next").hasRole("ADMIN")
                //.and()
                //.rememberMe() //记住我功能
                //.tokenValiditySeconds(10000);


                //自定义的拦截器 , 在适当的地方加入
                http.addFilterAt(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);

                http.csrf().disable(); ///关闭默认的csrf认证
    }


}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

破壁书

破壁书

邵燕君 主编、王玉玊 副主编 / 生活•读书•新知三联书店 生活书店出版有限公司 / 2018-6-1 / 88.00

*一本神奇的网络文化辞典,解读二次元、宅文化、网文、游戏、流行文化,让人大开眼界; *245个网络文化核心关键词,追本溯源,讲述背后文化演变与有趣故事,读来恍然大悟,知其然,更知其所以然; *北大中文系学术团队数年研究成果,曹文轩、韩少功、李敬泽、猫腻顾问推荐,三联生活书店花3年倾力打造; *百度 查不到、词条不过时、形式新颖丰富、文章可读性强、学术上经得起推敲,五大特点打造权威......一起来看看 《破壁书》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具