日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

spring security 学习一

發(fā)布時間:2024/9/15 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 spring security 学习一 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

spring security 學(xué)習(xí)一

1、配置基本的springboot web項目,加入security5依賴,啟動項目

瀏覽器訪問,即可出現(xiàn)一個默認(rèn)的登錄頁面

2、什么都沒有配置 登錄頁面哪里來的

一般不知從何入手,就看官方文檔里是如何做的,官方的文檔和api 是最好最完整的介紹和參考,點(diǎn)擊鏈接查看官方文檔地址

(https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#jc-oauth2login),或者通過Google 搜索 spring security,

在結(jié)果中點(diǎn)擊Spring Security Reference,點(diǎn)擊進(jìn)入頁面,然后就可以看到關(guān)于Spring Security的文檔;

通過查看文檔發(fā)現(xiàn),WebSecurityConfigurerAdapter 提供的默認(rèn)的配置,config(HttpSecurty http)中的formLogin(),這個方法內(nèi)容如下:

public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {return (FormLoginConfigurer)this.getOrApply(new FormLoginConfigurer());}

查看formLogin()源碼,跳轉(zhuǎn)到HttpSecurity類中,這個方法返回一個FormLoginConfigurer<HttpSercurity>類型的數(shù)據(jù)。再繼續(xù)來看看這

個FormLoginConfigurer,在FormLoginConfigurer中有個initDefaultLoginFilter()方法:

private void initDefaultLoginFilter(H http) {DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = (DefaultLoginPageGeneratingFilter)http.getSharedObject(DefaultLoginPageGeneratingFilter.class);if (loginPageGeneratingFilter != null && !this.isCustomLoginPage()) {loginPageGeneratingFilter.setFormLoginEnabled(true);loginPageGeneratingFilter.setUsernameParameter(this.getUsernameParameter());loginPageGeneratingFilter.setPasswordParameter(this.getPasswordParameter());loginPageGeneratingFilter.setLoginPageUrl(this.getLoginPage());loginPageGeneratingFilter.setFailureUrl(this.getFailureUrl());loginPageGeneratingFilter.setAuthenticationUrl(this.getLoginProcessingUrl());}}

這個方法,初始化一個默認(rèn)登錄頁的過濾器,可以看到第一句代碼,默認(rèn)的過濾器是DefaultLoginPageGeneratingFilter,下面是設(shè)置一些必要的參數(shù),進(jìn)入到這個過濾器中:

在描述中可以看到,如果沒有配置login頁,這個過濾器會被創(chuàng)建,過濾器創(chuàng)建后再瀏覽器訪問的時候回指定doFilter()方法:

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);}

登錄頁面的配置是通過generateLoginPageHtml()方法創(chuàng)建的,再來看看這個方法內(nèi)容:

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");}sb.append("</div>\n");sb.append("</body></html>");return sb.toString();}

?

3、去掉默認(rèn)的登錄頁,修改application.yml,添加一下內(nèi)容(在security5中不在支持以下配置,而是提供一個自定義的WebSecurityConfigurer文件)

The security auto-configuration is no longer customizable. Provide your own WebSecurityConfigurer bean instead. security:basic:enabled: false

?

4、自定義WebSecurityConfigurer

以下配置是創(chuàng)建一個最簡單的基于form表單認(rèn)證的security

formLogin():指定認(rèn)證為form表單

authorizeRequests():授權(quán)

anyRequest():任何請求

authenticated():都需要認(rèn)證

@Configuration public class CusWebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()//指定是表單登錄.and().authorizeRequests()//授權(quán).anyRequest()//任何請求.authenticated();//都需要身份認(rèn)證} }

?

5、基本流程

過濾器鏈有以下:

①UsernamePasswordAuthenticationFilter

  在過濾器容器中判斷請求中是否有用戶名和密碼,如果有用戶名和密碼就會使用UsernamePasswordAuthenticationFilter這個過濾器,如果沒有就會走下一個過濾器

②BasicAuthenticationFilter

  在這個過濾器中回嘗試獲取請求頭中是否有basic開頭的Authentication信息,如果有

就會嘗試解碼,處理完成之后會走下一個filter

③ExceptionTranslationFilter

?? 這個過濾器的作用是用來捕獲下邊這個FilterSecurityInterceptor拋出的異常

④FilterSecurityInterceptor

  這個攔截器是過濾器鏈中的最后一環(huán),在這個里邊會判斷當(dāng)前請求能否訪問controller,

能否訪問是根據(jù)securityconfig配置來判斷的

即:

?

?6、源碼學(xué)習(xí)

FilterSecurityInterceptor關(guān)鍵源碼

在invoke方法中有一個super.beforeInvocation方法,如上圖,綠色的過濾器鏈都是在這個方法中進(jìn)行處理的

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 checkingfi.getChain().doFilter(fi.getRequest(), fi.getResponse());}else {// first time this request being called, so perform security checkingif (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);}}

當(dāng)訪問api:localhost:9999/h? 時,請求回走到FIlterSecurityInterceptor中的beforeInvocation處

因為自己的securityConfig的是所有的請求都需要進(jìn)行認(rèn)證,因此在執(zhí)行befoeInvocation的時候會拋出一個異常,也就是進(jìn)入了ExceptionTranlationFilter中(因為沒有經(jīng)過認(rèn)證,不能訪問api)

在ExceptionTranlationFilter對異常進(jìn)行處理,也就是把請求重定向到login頁面上。

?

在登錄頁面填寫登錄名和密碼(user/4ed8dccc-9425-4f92-8b12-0bac0d88793b,密碼后臺日志會2自動輸出),填寫完畢后點(diǎn)擊登錄,又因為使用的是form表單登錄,所以會進(jìn)入到UserNamePasswordAuthenticationFilter中,

在UsernamepasswordAuthenticationFilter中執(zhí)行完畢后,回再次進(jìn)入到FilterSecurityInteceptor中的beforeInvocation處,此時執(zhí)行到該處是不會報錯,回向下繼續(xù)進(jìn)行。

?

調(diào)用doFilter,也就是進(jìn)入了自己寫的api接口中(controller中)

?

整個的流程:FilterSecurityInterceptor攔截請求,沒有認(rèn)證,重定向到默認(rèn)的form認(rèn)證頁面(login),在登錄頁面輸入用戶名密碼,點(diǎn)擊登錄后,會進(jìn)入到UsernamePasswordAuthenticationFilter中(因為使用的form表單認(rèn)證,如果使用其他認(rèn)證的話,會進(jìn)入到其他Filter中),在UsernamePasswordAuthenticationFilter中執(zhí)行完畢后,回再次進(jìn)入FIlterSecurityInterceptor中,執(zhí)行沒問題后,最終到controller層中的api處

?

?自定義認(rèn)證邏輯

一、處理用戶信息獲取邏輯

在security中用戶信息的獲取中,提供了一個接口UserDetailService,該接口中只有一個方法loadUserByUsername,返回參數(shù)是一個UserDetail

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

在獲取用戶信息是,只需要關(guān)注一點(diǎn)即,獲取UserDetail用戶信息,之后的認(rèn)證都是基于此對象的,

當(dāng)查不到一個username是,會拋出一個UsernameNotFoundException異常,可以進(jìn)行異常統(tǒng)一攔截

1、新建MyUserDetailsService(數(shù)據(jù)都是寫死的)

(ps:security5好像不能只寫一個userdetailservice就行運(yùn)行,也得配一個PasswordEncoder,即在security配置文件中添加)

@Component @Slf4j public class MyUserDetailsService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {//根據(jù)username查找用戶信息,在這,先手動寫一個user信息log.info("查找用戶信息{}", s);

     //密碼在security5中好像得加密? 不加密的話會爆粗(不確定)

       BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
      String password = encoder.encode("password");

    //這個user對象使用的是security中的user對象,此對象已經(jīng)實現(xiàn)了userDetail接口//user前兩個參數(shù)是進(jìn)行認(rèn)證的,第三個參數(shù)是當(dāng)前用戶所擁有的權(quán)限,security回根據(jù)授權(quán)代碼進(jìn)行驗證return new User(s, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); // AuthorityUtils.commaSeparatedStringToAuthorityList 這個方法是將一個字符一“,” 分割開} } //passwordEncoder @Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());}

添加w完畢后,訪問localhost:9999/h api,先回跳轉(zhuǎn)到默認(rèn)登錄頁面:

隨便輸入username和password:會出現(xiàn)bad credentials信息

如果密碼輸入password(后臺user自定義加密后的password):會登錄成功,并會執(zhí)行controller

?

二、處理用戶校驗邏輯

1、 用戶的校驗邏輯主要就是比較密碼是否匹配,這一塊有security自動匹配(即將user信息放到Userdetail解耦的實現(xiàn)類中即可)

2、賬號是否過期、是否被鎖定、是否可用,這幾個校驗都可以重新以下方法(如果沒有對應(yīng)的邏輯,永遠(yuǎn)返回true即可)

boolean isAccountNonExpired();//賬號沒有過期 如果不需要的話,改為true,沒有過期boolean isAccountNonLocked(); //賬號沒有鎖定鎖定boolean isCredentialsNonExpired();//密碼是否過期了boolean isEnabled();//這個可以配到庫中

3、自己測試

修改loadUserByUsername方法的返回參數(shù)

①accountNonLock設(shè)為false

@Component @Slf4j public class MyUserDetailsService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {//根據(jù)username查找用戶信息,在這,先手動寫一個user信息log.info("查找用戶信息{}", s);BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();String password = encoder.encode("password");//user前兩個參數(shù)是進(jìn)行認(rèn)證的,第三個參數(shù)是當(dāng)前用戶所擁有的權(quán)限,security回根據(jù)授權(quán)代碼進(jìn)行驗證return new User(s, password, true, true, true, false,AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); // AuthorityUtils.commaSeparatedStringToAuthorityList 這個方法是將一個字符一“,” 分割開} }

修改問之后,重啟測試用的項目后,方法api,輸入用戶密碼后,提示已被鎖定(即accountNonLock屬性被設(shè)為false)

?

三、處理密碼加密解密

以下兩種方式,都可以使用到加密

@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());}

或:

@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}

?

posted @ 2019-04-22 21:37 巡山小妖N 閱讀(...) 評論(...) 編輯 收藏

總結(jié)

以上是生活随笔為你收集整理的spring security 学习一的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。