SpringSecurity 默认表单登录页展示流程源码

SpringSecurity 默认表单登录页展示流程源码

本篇主要讲解 SpringSecurity提供的默认表单登录页 它是如何展示的的流程,
涉及

1.FilterSecurityInterceptor,
2.ExceptionTranslationFilc,xmccmc,ter ,
3.DefaultLoginPageGeneratingFilter 过滤器,
并且简单介绍了 AccessDecisionManager 投票机制

 1.准备工作(体验SpringSecurity默认表单认证)

  1.1 创建SpringSecurity项目

  先通过IDEA 创建一个SpringBoot项目 并且依赖SpringSecurity,Web依赖

  此时pom.xml会自动添加

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-security</artifactId>
</dependency>

  1.2 提供一个接口

@RestController
public class HelloController {

@RequestMapping("/hello")
public String hello() {
 return "Hello SpringSecurity";
 }
}

  1.3 启动项目

  直接访问 提供的接口

http://localhost:8080/hello

  会发现浏览器被直接重定向到了 /login 并且显示如下默认的表单登录页

http://localhost:8080/login

  1.4 登录

  在启动项目的时候 控制台会打印一个 seuciryt password : xxx

Using generated security password: f520875f-ea2b-4b5d-9b0c-f30c0c17b90b

  直接登录

用户名:user 密码 :f520875f-ea2b-4b5d-9b0c-f30c0c17b90b

  登录成功并且 浏览器又会重定向到 刚刚访问的接口

 2.springSecurityFilterchain 过滤器链

 如果你看过我另一篇关于SpringSecurity初始化源码的博客,那么你一定知道当SpringSecurity项目启动完成后会初始化一个 springSecurityFilterchain 它内部 additionalFilters属性初始化了很多Filter 如下
所有的请求都会经过这一系列的过滤器 Spring Security就是通过这些过滤器 来进行认证授权等

 3.FilterSecurityInterceptor (它会判断这次请求能否通过)

 FilterSecurityInterceptor是过滤器链中最后一个过滤器,主要用于判断请求能否通过,内部通过AccessDecisionManager 进行投票判断

 当我们未登录访问

http://localhost:8080/hello

 请求会被 FilterSecurityInterceptor 拦截

public void doFilter(ServletRequest request, ServletResponse response,
 FilterChain chain) throws IOException, ServletException {
 FilterInvocation fi = new FilterInvocation(request, response, chain);
 invoke(fi);
}

 重点看invoke方法

public void invoke(FilterInvocation fi) throws IOException, ServletException {
 if ((fi.getRequest() != null)
  && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
  && observeOncePerRequest) {
 // filter already applied to this request and user wants us to observe
 // once-per-request handling, so don't re-do security checking
 fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
 }
 else {
 // first time this request being called, so perform security checking
 if (fi.getRequest() != null && observeOncePerRequest) {
  fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
 }

 InterceptorStatusToken token = super.beforeInvocation(fi);

 try {
  fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
 }
 finally {
  super.finallyInvocation(token);
 }

 super.afterInvocation(token, null);
 }
}

 源码中有这样一句,其实就是判断当前用户是否能够访问指定的接口,可以则执行 fi.getChain().doFilter 调用访问的接口
否则 内部会抛出异常

InterceptorStatusToken token = super.beforeInvocation(fi);

 beforeInvocation 方法内部是通过 accessDecisionManager 去做决定的
 Spring Security已经内置了几个基于投票的AccessDecisionManager包括(AffirmativeBased ,ConsensusBased ,UnanimousBased)当然如果需要你也可以实现自己的AccessDecisionManager

 使用这种方式,一系列的AccessDecisionVoter将会被AccessDecisionManager用来对Authentication是否有权访问受保护对象进行投票,然后再根据投票结果来决定是否要抛出AccessDeniedException

this.accessDecisionManager.decide(authenticated, object, attributes);

 AffirmativeBased的 decide的实现如下

public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
 int deny = 0;
 Iterator var5 = this.getDecisionVoters().iterator();

 while(var5.hasNext()) {
 AccessDecisionVoter voter = (AccessDecisionVoter)var5.next();
 int result = voter.vote(authentication, object, configAttributes);
 if (this.logger.isDebugEnabled()) {
  this.logger.debug("Voter: " + voter + ", returned: " + result);
 }

 switch(result) {
 case -1:
  ++deny;
  break;
 case 1:
  return;
 }
 }

 if (deny > 0) {
 throw new AccessDeniedException(this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
 } else {
 this.checkAllowIfAllAbstainDecisions();
 }
}

 AffirmativeBased的逻辑是这样的:

(1)只要有AccessDecisionVoter的投票为ACCESS_GRANTED则同意用户进行访问;
(2)如果全部弃权也表示通过;
(3)如果没有一个人投赞成票,但是有人投反对票,则将抛出AccessDeniedException。

 当我们第一次访问的时候

http://localhost:8080/hello的时候

 返回 result = -1 会抛出 AccessDeniedException 拒绝访问异常

 4.ExceptionTranslationFilter (捕获AccessDeniedException异常)

 该过滤器它会接收到FilterSecurityInterceptor抛出的 AccessDeniedException异常)并且进行捕获,然后发送重定向到/login请求

 源码如下:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
 throws IOException, ServletException {
 HttpServletRequest request = (HttpServletRequest) req;
 HttpServletResponse response = (HttpServletResponse) res;

 try {
 chain.doFilter(request, response);

 logger.debug("Chain processed normally");
 }
 catch (IOException ex) {
 throw ex;
 }
 catch (Exception ex) {
 // Try to extract a SpringSecurityException from the stacktrace
 Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
 RuntimeException ase = (AuthenticationException) throwableAnalyzer
  .getFirstThrowableOfType(AuthenticationException.class, causeChain);

 if (ase == null) {
  ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
   AccessDeniedException.class, causeChain);
 }

 if (ase != null) {
  if (response.isCommitted()) {
  throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
  }
  handleSpringSecurityException(request, response, chain, ase);
 }
 else {
  // Rethrow ServletExceptions and RuntimeExceptions as-is
  if (ex instanceof ServletException) {
  throw (ServletException) ex;
  }
  else if (ex instanceof RuntimeException) {
  throw (RuntimeException) ex;
  }

  // Wrap other Exceptions. This shouldn't actually happen
  // as we've already covered all the possibilities for doFilter
  throw new RuntimeException(ex);
 }
 }
}

 当获取异常后 调用

handleSpringSecurityException(request, response, chain, ase);

 handleSpringSecurityException 源码如下:

private void handleSpringSecurityException(HttpServletRequest request,
 HttpServletResponse response, FilterChain chain, RuntimeException exception)
 throws IOException, ServletException {
 if (exception instanceof AuthenticationException) {
 logger.debug(
  "Authentication exception occurred; redirecting to authentication entry point",
  exception);

 sendStartAuthentication(request, response, chain,
  (AuthenticationException) exception);
 }
 else if (exception instanceof AccessDeniedException) {
 Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
 if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
  logger.debug(
   "Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
   exception);

  sendStartAuthentication(
   request,
   response,
   chain,
   new InsufficientAuthenticationException(
   messages.getMessage(
    "ExceptionTranslationFilter.insufficientAuthentication",
    "Full authentication is required to access this resource")));
 }
 else {
  logger.debug(
   "Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
   exception);

  accessDeniedHandler.handle(request, response,
   (AccessDeniedException) exception);
 }
 }
}

 先判断获取的异常是否是AccessDeniedException 再判断是否是匿名用户,如果是则调用 sendStartAuthentication 重定向到登录页面

 重定向登录页面之前会保存当前访问的路径,这就是为什么我们访问 /hello接口后 再登录成功后又会跳转到 /hello接口,因为在重定向到/login接口前 这里进行了保存 requestCache.saveRequest(request, response);

protected void sendStartAuthentication(HttpServletRequest request,
 HttpServletResponse response, FilterChain chain,
 AuthenticationException reason) throws ServletException, IOException {
 // SEC-112: Clear the SecurityContextHolder's Authentication, as the
 // existing Authentication is no longer considered valid
 SecurityContextHolder.getContext().setAuthentication(null);
 requestCache.saveRequest(request, response);
 logger.debug("Calling Authentication entry point.");
 authenticationEntryPoint.commence(request, response, reason);
}

 authenticationEntryPoint.commence(request, response, reason);方法内部

 调用LoginUrlAuthenticationEntryPoint 的 commence方法

 LoginUrlAuthenticationEntryPoint 的commence方法内部有 构造重定向URL的方法

redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);

protected String buildRedirectUrlToLoginPage(HttpServletRequest request,
 HttpServletResponse response, AuthenticationException authException) {

 String loginForm = determineUrlToUseForThisRequest(request, response,
  authException);

protected String determineUrlToUseForThisRequest(HttpServletRequest request,
 HttpServletResponse response, AuthenticationException exception) {

 return getLoginFormUrl();
}

 最终会获取到需要重定向的URL /login

 然后sendRedirect 既会重定向到 /login 请求

 5.DefaultLoginPageGeneratingFilter (会捕获重定向的/login 请求)

 DefaultLoginPageGeneratingFilter是过滤器链中的一个用于捕获/login请求,并且渲染出一个默认表单页面

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
 throws IOException, ServletException {
 HttpServletRequest request = (HttpServletRequest) req;
 HttpServletResponse response = (HttpServletResponse) res;

 boolean loginError = isErrorPage(request);
 boolean logoutSuccess = isLogoutSuccess(request);
 if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
 String loginPageHtml = generateLoginPageHtml(request, loginError,
  logoutSuccess);
 response.setContentType("text/html;charset=UTF-8");
 response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
 response.getWriter().write(loginPageHtml);

 return;
 }

 chain.doFilter(request, response);
}

 isLoginUrlRequest 判断请求是否是 loginPageUrl

private boolean isLoginUrlRequest(HttpServletRequest request) {
 return matches(request, loginPageUrl);
}

 因为我们没有配置所以 默认的 loginPageUrl = /login

 验证通过请求路径 能匹配 loginPageUrl

String loginPageHtml = generateLoginPageHtml(request, loginError,
  logoutSuccess);

 generateLoginPageHtml 绘制默认的HTML 页面,到此我们默认的登录页面怎么来的就解释清楚了

private String generateLoginPageHtml(HttpServletRequest request, boolean loginError,
  boolean logoutSuccess) {
 String errorMsg = "Invalid credentials";

 if (loginError) {
  HttpSession session = request.getSession(false);

  if (session != null) {
   AuthenticationException ex = (AuthenticationException) session
     .getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
   errorMsg = ex != null ? ex.getMessage() : "Invalid credentials";
  }
 }

 StringBuilder sb = new StringBuilder();

 sb.append("<!DOCTYPE html>\n"
   + "<html lang=\"en\">\n"
   + " <head>\n"
   + " <meta charset=\"utf-8\">\n"
   + " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"
   + " <meta name=\"description\" content=\"\">\n"
   + " <meta name=\"author\" content=\"\">\n"
   + " <title>Please sign in</title>\n"
   + " <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
   + " <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
   + " </head>\n"
   + " <body>\n"
   + "  <div class=\"container\">\n");

 String contextPath = request.getContextPath();
 if (this.formLoginEnabled) {
  sb.append("  <form class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.authenticationUrl + "\">\n"
    + "  <h2 class=\"form-signin-heading\">Please sign in</h2>\n"
    + createError(loginError, errorMsg)
    + createLogoutSuccess(logoutSuccess)
    + "  <p>\n"
    + "   <label for=\"username\" class=\"sr-only\">Username</label>\n"
    + "   <input type=\"text\" id=\"username\" name=\"" + this.usernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
    + "  </p>\n"
    + "  <p>\n"
    + "   <label for=\"password\" class=\"sr-only\">Password</label>\n"
    + "   <input type=\"password\" id=\"password\" name=\"" + this.passwordParameter + "\" class=\"form-control\" placeholder=\"Password\" required>\n"
    + "  </p>\n"
    + createRememberMe(this.rememberMeParameter)
    + renderHiddenInputs(request)
    + "  <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
    + "  </form>\n");
 }

 if (openIdEnabled) {
  sb.append("  <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.openIDauthenticationUrl + "\">\n"
    + "  <h2 class=\"form-signin-heading\">Login with OpenID Identity</h2>\n"
    + createError(loginError, errorMsg)
    + createLogoutSuccess(logoutSuccess)
    + "  <p>\n"
    + "   <label for=\"username\" class=\"sr-only\">Identity</label>\n"
    + "   <input type=\"text\" id=\"username\" name=\"" + this.openIDusernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
    + "  </p>\n"
    + createRememberMe(this.openIDrememberMeParameter)
    + renderHiddenInputs(request)
    + "  <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
    + "  </form>\n");
 }

 if (oauth2LoginEnabled) {
  sb.append("<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h2>");
  sb.append(createError(loginError, errorMsg));
  sb.append(createLogoutSuccess(logoutSuccess));
  sb.append("<table class=\"table table-striped\">\n");
  for (Map.Entry<String, String> clientAuthenticationUrlToClientName : oauth2AuthenticationUrlToClientName.entrySet()) {
   sb.append(" <tr><td>");
   String url = clientAuthenticationUrlToClientName.getKey();
   sb.append("<a href=\"").append(contextPath).append(url).append("\">");
   String clientName = HtmlUtils.htmlEscape(clientAuthenticationUrlToClientName.getValue());
   sb.append(clientName);
   sb.append("</a>");
   sb.append("</td></tr>\n");
  }
  sb.append("</table>\n");
 }

 if (this.saml2LoginEnabled) {
  sb.append("<h2 class=\"form-signin-heading\">Login with SAML 2.0</h2>");
  sb.append(createError(loginError, errorMsg));
  sb.append(createLogoutSuccess(logoutSuccess));
  sb.append("<table class=\"table table-striped\">\n");
  for (Map.Entry<String, String> relyingPartyUrlToName : saml2AuthenticationUrlToProviderName.entrySet()) {
   sb.append(" <tr><td>");
   String url = relyingPartyUrlToName.getKey();
   sb.append("<a href=\"").append(contextPath).append(url).append("\">");
   String partyName = HtmlUtils.htmlEscape(relyingPartyUrlToName.getValue());
   sb.append(partyName);
   sb.append("</a>");
   sb.append("</td></tr>\n");
  }
  sb.append("</table>\n");
 }
 sb.append("</div>\n");
 sb.append("</body></html>");

 return sb.toString();
}

至此 SpringSecurity 默认表单登录页展示流程源码部分已经全部讲解完毕,会渲染出下面的页面,但是一定要有网的情况,否则样式可能会变化

6.总结

本篇主要讲解 SpringSecurity提供的默认表单登录页 它是如何展示的的流程,包括涉及这一流程中相关的 3个过滤器

1.FilterSecurityInterceptor,
2.ExceptionTranslationFilter ,
3.DefaultLoginPageGeneratingFilter 过滤器,
并且简单介绍了一下 AccessDecisionManager 它主要进行投票来判断该用户是否能够访问相应的 资源
AccessDecisionManager 投票机制我也没有深究 后续我会详细深入一下再展开

以上所述是小编给大家介绍的SpringSecurity 默认表单登录页展示流程源码,希望对大家有所帮助!

(0)

相关推荐

  • SpringBoot+Spring Security+JWT实现RESTful Api权限控制的方法

    摘要:用spring-boot开发RESTful API非常的方便,在生产环境中,对发布的API增加授权保护是非常必要的.现在我们来看如何利用JWT技术为API增加授权保护,保证只有获得授权的用户才能够访问API. 一:开发一个简单的API 在IDEA开发工具中新建一个maven工程,添加对应的依赖如下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-b

  • 详解spring security之httpSecurity使用示例

    httpSecurity 类似于spring security的xml配置文件命名空间配置中的<http>元素.它允许对特定的http请求基于安全考虑进行配置.默认情况下,适用于所有的请求,但可以使用requestMatcher(RequestMatcher)或者其它相似的方法进行限制. 使用示例: 最基本的基于表单的配置如下.该配置将所有的url访问权限设定为角色名称为"ROLE_USER".同时也定义了内存认证模式:使用用户名"user"和密码&qu

  • spring security自定义登录页面

    在项目中我们肯定不能使用Spring自己生成的登录页面,而要用我们自己的登录页面,下面讲一下如何自定义登录页面,先看下配置 <sec:http auto-config="true"> <sec:intercept-url pattern="/app.jsp" access="ROLE_SERVICE"/> <sec:intercept-url pattern="/**" access="

  • Spring Security OAuth2实现使用JWT的示例代码

    1.概括 在博客中,我们将讨论如何让Spring Security OAuth2实现使用JSON Web Tokens. 2.Maven 配置 首先,我们需要在我们的pom.xml中添加spring-security-jwt依赖项. <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> &l

  • Spring Security 表单登录功能的实现方法

    1.简介 本文将重点介绍使用 Spring Security 登录. 本文将构建在之前简单的 Spring MVC示例 之上,因为这是设置Web应用程序和登录机制的必不可少的. 2. Maven 依赖 要将Maven依赖项添加到项目中,请参阅Spring Security with Maven 一文. 标准的 spring-security-web 和 spring-security-config 都是必需的. 3. Spring Security Java配置 我们首先创建一个扩展 WebSe

  • Spring Security在标准登录表单中添加一个额外的字段

    概述 在本文中,我们将通过向标准登录表单添加额外字段来实现Spring Security的自定义身份验证方案. 我们将重点关注两种不同的方法,以展示框架的多功能性以及我们可以使用它的灵活方式. 我们的第一种方法是一个简单的解决方案,专注于重用现有的核心Spring Security实现. 我们的第二种方法是更加定制的解决方案,可能更适合高级用例. 2. Maven设置 我们将使用Spring Boot启动程序来引导我们的项目并引入所有必需的依赖项.  我们将使用的设置需要父声明,Web启动器和安

  • SpringBoot + SpringSecurity 短信验证码登录功能实现

    实现原理 在之前的文章中,我们介绍了普通的帐号密码登录的方式: SpringBoot + Spring Security 基本使用及个性化登录配置. 但是现在还有一种常见的方式,就是直接通过手机短信验证码登录,这里就需要自己来做一些额外的工作了. 对SpringSecurity认证流程详解有一定了解的都知道,在帐号密码认证的过程中,涉及到了以下几个类:UsernamePasswordAuthenticationFilter(用于请求参数获取),UsernamePasswordAuthentica

  • SpringSecurity 默认表单登录页展示流程源码

    SpringSecurity 默认表单登录页展示流程源码 本篇主要讲解 SpringSecurity提供的默认表单登录页 它是如何展示的的流程, 涉及 1.FilterSecurityInterceptor, 2.ExceptionTranslationFilc,xmccmc,ter , 3.DefaultLoginPageGeneratingFilter 过滤器, 并且简单介绍了 AccessDecisionManager 投票机制  1.准备工作(体验SpringSecurity默认表单认证

  • vue项目实现表单登录页保存账号和密码到cookie功能

    实现功能: 1.一周内自动登录勾选时,将账号和密码保存到cookie,下次登陆自动显示到表单内 2.点击忘记密码则清空之前保存到cookie的值,下次登陆需要手动输入 次要的就不说了直接上主要的代码 html部分 <el-form :model="ruleForm" :rules="rules" ref="ruleForm" class="demo-ruleForm loginFrom"> <h1 style

  • SpringSecurity 自定义表单登录的实现

    本篇主要讲解 在SpringSecurity中 如何 自定义表单登录 , SpringSecurity默认提供了一个表单登录,但是实际项目里肯定无法使用的,本篇就主要讲解如何自定义表单登录 1.创建SpringSecurity项目 1.1 使用IDEA 先通过IDEA 创建一个SpringBoot项目 并且依赖SpringSecurity,Web依赖 此时pom.xml会自动添加 <dependency> <groupId>org.springframework.boot</

  • SpringBoot基于SpringSecurity表单登录和权限验证的示例

    一.简介 上篇介绍了一个自己做的管理系统,最近空闲的时间自己在继续做,把之前登录时候自定义的拦截器过滤器换成了基于SpringSecurity来做,其中遇到了很多坑,总结下,大家有遇到类似问题的话就当是为大家闭坑吧. 二.项目实现功能和成果展示 首先来看下登录界面:这是我输入的一个正确的信息,点击登录后SpringSecurity会根据你输入的用户名和密码去验证是否正确,如果正确的话就去你定义的页面,我这里定义的是查询教师信息页面.来看下代码吧. 三.准备工作(前台页面.实体类) 实体类Teac

  • SpringSecurity 表单登录的实现

    目录 表单登录 登录成功 登录失败 注销登录 自定义注销成功的返回内容 表单登录 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated()

  • Linux curl表单登录或提交与cookie使用详解

    前言 本文主要讲解通过curl 实现表单提交登录.单独的表单提交与表单登录都差不多,因此就不单独说了. 说明:针对curl表单提交实现登录,不是所有网站都适用,原因是有些网站后台做了限制或有其他校验.我们不知道这些网站后台的限制或校验机制具体是什么,因此直接curl表单登录可能是不行的. 当然,如下案例是可以用curl登录的. 案例:LeanCloud登录 要求和结果 要求:通过curl登录后,能正常访问leancloud的应用页面. 登录页面链接如下: 1 https://leancloud.

  • 浅析Spring Security登录验证流程源码

    一.登录认证基于过滤器链 Spring Security的登录验证流程核心就是过滤器链.当一个请求到达时按照过滤器链的顺序依次进行处理,通过所有过滤器链的验证,就可以访问API接口了. SpringSecurity提供了多种登录认证的方式,由多种Filter过滤器来实现,比如: BasicAuthenticationFilter实现的是HttpBasic模式的登录认证 UsernamePasswordAuthenticationFilter实现用户名密码的登录认证 RememberMeAuthe

  • Spring Security 实现用户名密码登录流程源码详解

    目录 引言 探究 登录流程 校验 用户信息保存 引言 你在服务端的安全管理使用了 Spring Security,用户登录成功之后,Spring Security 帮你把用户信息保存在 Session 里,但是具体保存在哪里,要是不深究你可能就不知道, 这带来了一个问题,如果用户在前端操作修改了当前用户信息,在不重新登录的情况下,如何获取到最新的用户信息? 探究 无处不在的 Authentication 玩过 Spring Security 的小伙伴都知道,在 Spring Security 中

  • 详解Android布局加载流程源码

    一.首先看布局层次 看这么几张图 我们会发现DecorView里面包裹的内容可能会随着不同的情况而变化,但是在Decor之前的层次关系都是固定的.即Activity包裹PhoneWindow,PhoneWindow包裹DecorView.接下来我们首先看一下三者分别是如何创建的. 二.Activity是如何创建的 首先看到入口类ActivityThread的performLaunchActivity方法: private Activity performLaunchActivity(Activi

随机推荐