認証コードモードに転換のOAuth2認証モードパスワードモードに前、前とセッションベースのマイクロサービスSSO後の分離構造の下端を実現します。ユーザーは、ログインクライアント上のログインページをクリックし、コールバックメソッドコールバッククライアントアプリケーションに認証サーバ、ログインが成功すると、認証サーバへのログインにジャンプし、認証コードを運ぶ、クライアントは認証サーバ交換に認証コードを保持しますaccess_tokenはは、access_tokenはは自分のセッションを維持取得するためのクライアントの後に、ユーザが正常にログインしている前提としています。
このプロセスの最上部にあるセッションベースのSSO 3つの寿命を持ち、:
1、クライアントアプリケーションの妥当性のセッションでは、認証サーバに一度どのくらいジャンプを制御します
2、セッションの認証サーバは、ユーザ名とパスワードを入力するようにユーザにかかる時間有効で、コントロールであります
3、有効なaccess_tokenは、アクセス制御どのくらいのマイクロサービスに一度ログインします
記事は現在あり、一連の問題は、このような出口のクリックなど、まだですが、クライアントアプリケーションのセッションはセッション認証サーバの失敗を失敗し、ない、ユーザーがログアウトし、認証にリダイレクトログインボタンをクリックし、上記の認証サーバが自動的にクライアントにコールバックしますので、サーバ、認証サーバは、セッションは、失敗していないため、クライアントのパフォーマンスは、直接であるとユーザーに終了ボタンをポイントする印象を与えて、ログインしますが、道をしませんでした。セッションクライアントと認証サーバの有効期限が切れているときにこの問題を解決するには、以下の、アイデアはシンプルで、[終了]ボタンをクリックしてください。コードを書き始めましょう。
ログ処理ロジック
[終了]ボタンプロセス:
1、セッション独自のクライアントアプリケーションの障害
2、セッション認証サーバの障害、
このように、終了ボタンは、プロンプトへのデフォルト認証サーバーである、クライアントセッションに障害が発生した後、再びクリックすると、認証サーバにジャンプされます
[OK]をクリックし、ページがデフォルトのログイン・ページの認証サーバに残っています:
ユーザー名(カジュアル)、パスワードを(123456認証サーバは、ハードコード)を入力し、サインインをクリックし、
会跳转到了认证服务器默认的首页,没有,所以出现了404。
为什么直接在客户端应用点击登录按钮,登录成功后就可以跳回到客户端应用?看一下在客户端应用点击登录按钮的处理:
里面有一个 redirect_uri 参数,这样的请求,认证服务器在登录成功后,就知道要跳转到redirect_uri 。但是点击退出后出现的登录页面 ,是由【退出】触发的,认证服务器是不知道登录成功后要跳转到admin应用的。所以,要做退出的处理,让认证服务器知道,退出后要跳转到指定的uri,思路就是在退出的请求上,加一个 redirect_uri的参数,重写认证服务器的退出逻辑,退出后跳转到redirect_uri 即可。
请求认证服务器的退出逻辑的请求上,加上 redirect_uri=http://admin.nb.com:8080/index
在认证服务器上找到Spring处理退出逻辑的过滤器 org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter :
/** * Generates a default log out page. * * @author Rob Winch * @since 5.1 */ public class DefaultLogoutPageGeneratingFilter extends OncePerRequestFilter { private RequestMatcher matcher = new AntPathRequestMatcher("/logout", "GET"); private Function<HttpServletRequest, Map<String, String>> resolveHiddenInputs = request -> Collections .emptyMap(); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (this.matcher.matches(request)) { renderLogout(request, response); } else { filterChain.doFilter(request, response); } } private void renderLogout(HttpServletRequest request, HttpServletResponse response) throws IOException { String page = "<!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>Confirm Log Out?</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" + " <form class=\"form-signin\" method=\"post\" action=\"" + request.getContextPath() + "/logout\">\n" + " <h2 class=\"form-signin-heading\">Are you sure you want to log out?</h2>\n" + renderHiddenInputs(request) + " <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Log Out</button>\n" + " </form>\n" + " </div>\n" + " </body>\n" + "</html>"; response.setContentType("text/html;charset=UTF-8"); response.getWriter().write(page); } /** * Sets a Function used to resolve a Map of the hidden inputs where the key is the * name of the input and the value is the value of the input. Typically this is used * to resolve the CSRF token. * @param resolveHiddenInputs the function to resolve the inputs */ public void setResolveHiddenInputs( Function<HttpServletRequest, Map<String, String>> resolveHiddenInputs) { Assert.notNull(resolveHiddenInputs, "resolveHiddenInputs cannot be null"); this.resolveHiddenInputs = resolveHiddenInputs; } private String renderHiddenInputs(HttpServletRequest request) { StringBuilder sb = new StringBuilder(); for (Map.Entry<String, String> input : this.resolveHiddenInputs.apply(request).entrySet()) { sb.append("<input name=\"").append(input.getKey()).append("\" type=\"hidden\" value=\"").append(input.getValue()).append("\" />\n"); } return sb.toString(); } }
1,重写退出表单源码
这就是处理退出逻辑的过滤器,其中的html就是之前看到的让用户确认退出的页面,在认证服务器项目新建一个一模一样的包,将上边的类copy进去,由于java的类加载机制,自己写的类会优先于spring的类加载,java会加载我们自己写的类,而不加载spring包里的类:
重写后的类源码:
package org.springframework.security.web.authentication.ui; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Collections; import java.util.Map; import java.util.function.Function; /** * 重写退出逻辑,由于java的类加载机制,会优先执行自己的类,就不加载spring的了 * 这里有一个默认的 确认退出页面,可以定制 * 这里注释掉确认退出的提示语,直接写一段js脚本,提交退出表单 * 从request里获取到退出逻辑携带的 redirect_uri 参数,放入退出表单的隐藏input, * 这样在重写退出成功handler时,可以拿出这个参数,做跳转 * Generates a default log out page. * * @author Rob Winch * @since 5.1 */ public class DefaultLogoutPageGeneratingFilter extends OncePerRequestFilter { private RequestMatcher matcher = new AntPathRequestMatcher("/logout", "GET"); private Function<HttpServletRequest, Map<String, String>> resolveHiddenInputs = request -> Collections .emptyMap(); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (this.matcher.matches(request)) { renderLogout(request, response); } else { filterChain.doFilter(request, response); } } private void renderLogout(HttpServletRequest request, HttpServletResponse response) throws IOException { String page = "<!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>Confirm Log Out?</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" + " <form id=\"logoutForm\" class=\"form-signin\" method=\"post\" action=\"" + request.getContextPath() + "/logout\">\n" // + " <h2 class=\"form-signin-heading\">Are you sure you want to log out?</h2>\n" + renderHiddenInputs(request) // + " <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Log Out</button>\n" + "<input type='hidden' name='redirect_uri' value="+request.getParameter("redirect_uri")+"/>" + "<script>document.getElementById('logoutForm').submit()</script>" + " </form>\n" + " </div>\n" + " </body>\n" + "</html>"; response.setContentType("text/html;charset=UTF-8"); response.getWriter().write(page); } /** * Sets a Function used to resolve a Map of the hidden inputs where the key is the * name of the input and the value is the value of the input. Typically this is used * to resolve the CSRF token. * @param resolveHiddenInputs the function to resolve the inputs */ public void setResolveHiddenInputs( Function<HttpServletRequest, Map<String, String>> resolveHiddenInputs) { Assert.notNull(resolveHiddenInputs, "resolveHiddenInputs cannot be null"); this.resolveHiddenInputs = resolveHiddenInputs; } private String renderHiddenInputs(HttpServletRequest request) { StringBuilder sb = new StringBuilder(); for (Map.Entry<String, String> input : this.resolveHiddenInputs.apply(request).entrySet()) { sb.append("<input name=\"").append(input.getKey()).append("\" type=\"hidden\" value=\"").append(input.getValue()).append("\" />\n"); } return sb.toString(); } }
上述代码中的荧光绿色的是注释掉的两行代码,这两行代码是说显示的让用户确认退出登录的提示,这里给注释掉,让用户看不到退出提醒
上述代码中的荧光黄色的是新添加的代码,给退出登录的表单加了个id,然后在表单里写了个隐藏域,name= redirect_uri,值从request里获取,用于自定义退出成功Handler里,可以重定向到该路径。最后新增一个JavaScript脚本,自动提交表单。
如果你就想给用户一个退出提示,可以重写这个表单的样式。
2,下面自定义退出登录成功处理器
3,配置退出成功处理器
实验
启动四个微服务
访问客户端应用 http://admin.nb.com:8080/index/
点击去登录,跳转到了认证服务器的登录页面
登录成功,回调到客户端应用admin,点击获取订单信息,获取到了订单信息
点击退出登录,先是在客户端应用将session失效,然后再去认证服务器上做退出登录操作()
然后又跳转到了客户端应用的index页
总结
本篇解决了上篇遗留的问题(点击退出登录只是在客户端应用做session失效操作,当再次点击登录后,由于认证服务器的session还有效,用户不用输入用户名密码直接就登录了,给人的感觉是没有彻底退出去)。
本节在客户端应用做退出操作的同时,也在认证服务器上将session失效掉,让用户彻底退出登录。思路是在点击【退出登录】按钮的同时做两件事,一是让客户端应用的session失效,然后再发一个请求到认证服务器的 /logout 端点,这是spring OAuth自带的退出登录过滤器,同时并携带一个redirect_uri参数,让认证服务器退出登录之后,知道跳转到客户端应用去。否则认证服务器默认的退出逻辑是,退出后跳转到了认证服务器的首页,由于没有做首页,所以返回了一个404,我们重写了退出登录类org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter ,让退出登录表单自动提交,实现了退出成功handler,重定向到了客户端应用退出时携带过来的redirect_uri。
本篇代码github : https://github.com/lhy1234/springcloud-security/tree/chapt-5-3-sso-session 如果帮到了你,给个小星星吧