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

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

内容简介:用户登录基本流程处理如下: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认证
    }


}

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

查看所有标签

猜你喜欢:

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

Design Accessible Web Sites

Design Accessible Web Sites

Jeremy Sydik / Pragmatic Bookshelf / 2007-11-05 / USD 34.95

It's not a one-browser web anymore. You need to reach audiences that use cell phones, PDAs, game consoles, or other "alternative" browsers, as well as users with disabilities. Legal requirements for a......一起来看看 《Design Accessible Web Sites》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

MD5 加密
MD5 加密

MD5 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试