SpringSecurity自定義Form表單使用方法講解
背景
本系列教程,是作為團隊內(nèi)部的培訓資料準備的。主要以實驗的方式來體驗SpringSecurity
的各項Feature。
新建一個SpringBoot
項目,起名springboot-security-form
,核心依賴為Web
,SpringSecurity
與Thymeleaf
。
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency> </dependencies>
實驗-HttpBasic
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception {// There is no PasswordEncoder mapped for the id "null"PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();String yourPassword = "123";System.out.println("Encoded password: " + encoder.encode(yourPassword));// Config account info and permissionsauth.inMemoryAuthentication().withUser("dev").password(encoder.encode(yourPassword)).authorities("p1").and().withUser("test").password(encoder.encode(yourPassword)).authorities("p2"); } @Override protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/user/add").hasAuthority("p1").antMatchers("/user/query").hasAuthority("p2").antMatchers("/user/**").authenticated().anyRequest().permitAll() // Let other request pass.and().httpBasic(); }
實驗-自定義登錄頁面
登錄頁面配置
@Override protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/user/add").hasAuthority("p1").antMatchers("/user/query").hasAuthority("p2").antMatchers("/user/**").authenticated().anyRequest().permitAll() // Let other request pass.and().csrf().disable() // turn off csrf, or will be 403 forbidden.formLogin() // Support form and HTTPBasic.loginPage("/login"); }
后端登錄頁面接口
@Controller public class LoginController {@GetMapping("/login")public String login() {return "login";}@GetMapping(value = "/user/add")@ResponseBodypublic String accessResource1() {return " Access Resource 1: Add User";}@GetMapping(value = "/user/query")@ResponseBodypublic String accessResource2() {return " Access Resource 2: Query User";} }
前端模板
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Login</title> </head> <body> <form action="login" method="post"><span>用戶名</span><input type="text" name="username" /> <br><span>密碼</span><input type="password" name="password" /> <br><input type="submit" value="登錄"> </form> </body> </html>
Note:此時,需要先關閉CSRF,.csrf().disable()
,否則報403;
實驗-自定義登錄接口
默認登錄頁面接口與登錄數(shù)據(jù)提交接口是同一個:/login
,順著.loginPage
,進入FormLoginConfigurer
,源碼如下:
@Override public FormLoginConfigurer<H> loginPage(String loginPage) {return super.loginPage(loginPage); }
繼續(xù)進入父類的loginPage
方法,
protected T loginPage(String loginPage) {setLoginPage(loginPage);updateAuthenticationDefaults();this.customLoginPage = true;return getSelf(); }
繼續(xù)跟蹤進入方法updateAuthenticationDefaults();
,可以看到,如果沒有配置loginProcessingUrl
,那么loginProcessingUrl
與loginPage
便相同。
protected final void updateAuthenticationDefaults() {if (loginProcessingUrl == null) {loginProcessingUrl(loginPage);}if (failureHandler == null) {failureUrl(loginPage + "?error");}final LogoutConfigurer<B> logoutConfigurer = getBuilder().getConfigurer(LogoutConfigurer.class);if (logoutConfigurer != null && !logoutConfigurer.isCustomLogoutSuccess()) {logoutConfigurer.logoutSuccessUrl(loginPage + "?logout");} }
下面我們自定義登錄數(shù)據(jù)提交接口為/formLogin
,此時相應的前端action也要修改。
@Override protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/user/add").hasAuthority("p1").antMatchers("/user/query").hasAuthority("p2").antMatchers("/user/**").authenticated().anyRequest().permitAll() // Let other request pass.and().csrf().disable() // turn off csrf, or will be 403 forbidden.formLogin() // Support form and HTTPBasic.loginPage("/login").loginProcessingUrl("/formLogin"); }
<form action="formLogin" method="post"><span>用戶名</span><input type="text" name="username" /> <br><span>密碼</span><input type="password" name="password" /> <br><input type="submit" value="登錄"> </form>
實驗-自定義登錄數(shù)據(jù)參數(shù)
前面我們自定義了登錄頁面,在form
表單中設置用戶名、密碼分別為username
, password
,那為什么這樣寫呢,可以改成別的嘛?可以倒是可以,但是不能隨便改;
如果這里我們把username
改為name
,再次嘗試登錄,后端接口將報錯:org.springframework.security.authentication.BadCredentialsException: Bad credentials
。??墒菍嶋H項目中我們的用戶名密碼就是不叫這個名字呢?我們可以進行配置.usernameParameter("name")
:
@Override protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/user/add").hasAuthority("p1").antMatchers("/user/query").hasAuthority("p2").antMatchers("/user/**").authenticated().anyRequest().permitAll() // Let other request pass.and().csrf().disable() // turn off csrf, or will be 403 forbidden.formLogin() // Support form and HTTPBasic.loginPage("/login").loginProcessingUrl("/formLogin").usernameParameter("name"); }
<form action="formLogin" method="post"><span>用戶名</span><input type="text" name="name" /> <br><span>密碼</span><input type="password" name="password" /> <br><input type="submit" value="登錄"> </form>
默認的用戶名、密碼分別為username
, password
,我們看下SpringSecurity的源碼:
public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractAuthenticationFilterConfigurer<H, FormLoginConfigurer<H>, UsernamePasswordAuthenticationFilter> { /** * Creates a new instance * @see HttpSecurity#formLogin() */ public FormLoginConfigurer() { super(new UsernamePasswordAuthenticationFilter(), null); usernameParameter("username"); passwordParameter("password"); } }
實驗-自定義登錄失敗、成功處理器
問題:就以上個實驗3中的報錯信息為例,或當用戶名、密碼輸錯后,如何在后臺看到錯誤信息?
@Override protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/user/add").hasAuthority("p1").antMatchers("/user/query").hasAuthority("p2").antMatchers("/user/**").authenticated().anyRequest().permitAll() // Let other request pass.and().csrf().disable() // turn off csrf, or will be 403 forbidden.formLogin() // Support form and HTTPBasic.loginPage("/login").loginProcessingUrl("/formLogin").usernameParameter("name").failureHandler(new AuthenticationFailureHandler(){@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {exception.printStackTrace();request.getRequestDispatcher(request.getRequestURL().toString()).forward(request, response);}}); }
常見的認證異常,這里可以看到AuthenticationException
共有18個子類:
上述增加了在認證失敗時的處理:輸出錯誤信息。同理,如果想在登錄成功時直接進行一些處理(eg: 數(shù)據(jù)初始化等),可以使用以下配置:
.successHandler(new AuthenticationSuccessHandler() {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,Authentication authentication) throws IOException, ServletException {System.out.println("Login Successfully~");// do something here: initial work or forward to different url regarding different roles...request.getRequestDispatcher("").forward(request, response);} })
實驗-自定義登錄成功跳轉(zhuǎn)頁面
經(jīng)歷千難萬險,終于要登錄成功了。進來之后要跳轉(zhuǎn)到哪里呢?看你嘍~想跳哪里跳哪里。。
@Override protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/user/add").hasAuthority("p1").antMatchers("/user/query").hasAuthority("p2").antMatchers("/user/**").authenticated().anyRequest().permitAll() // Let other request pass.and().csrf().disable() // turn off csrf, or will be 403 forbidden.formLogin() // Support form and HTTPBasic.loginPage("/login").loginProcessingUrl("/formLogin").usernameParameter("name").failureHandler(new AuthenticationFailureHandler(){@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {exception.printStackTrace();request.getRequestDispatcher(request.getRequestURL().toString()).forward(request, response);}}).successForwardUrl("/ok"); // custom login success page, a POST request }
@Controller public class LoginController {...@PostMapping(value = "/ok")@ResponseBodypublic String ok() {return "ok";} }
通過.successForwardUrl("/ok")
配置了登錄成功之后要跳轉(zhuǎn)的頁面路徑或接口,同時需要在后端新增/ok
接口。
Note:
- 注意這里
successForwardUrl
的接口必須為POST
接口; - 除了
.successForwardUrl("/ok");
,還可以使用.defaultSuccessUrl("/ok");
或者.defaultSuccessUrl("/ok", true);
第二個參數(shù)true
表示不管是從哪個地址進來,登錄后全部跳轉(zhuǎn)到指定的地址,此時與successForwardUrl
效果相同,默認為false
- 當然,除了登錄成功后的跳轉(zhuǎn),還有登錄失敗后的跳轉(zhuǎn):
failureForwardUrl
。
實驗-自定義退出接口
默認的退出接口是/logout
,可進行配置:
@Override protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/user/add").hasAuthority("p1").antMatchers("/user/query").hasAuthority("p2").antMatchers("/user/**").authenticated().anyRequest().permitAll() // Let other request pass.and().csrf().disable() // turn off csrf, or will be 403 forbidden.formLogin() // Support form and HTTPBasic.loginPage("/login").loginProcessingUrl("/formLogin").usernameParameter("name").failureHandler(new AuthenticationFailureHandler(){@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {exception.printStackTrace();request.getRequestDispatcher(request.getRequestURL().toString()).forward(request, response);}}).successForwardUrl("/ok") // custom login success page, a POST request.and().logout().logoutUrl("/leave");}
上述配置將退出接口改為/leave
。在默認的退出過程中,還做了諸如清除認證信息和使Session失效等工作:
public class SecurityContextLogoutHandler implements LogoutHandler { protected final Log logger = LogFactory.getLog(this.getClass()); private boolean invalidateHttpSession = true; private boolean clearAuthentication = true; // ~ Methods // ========================================== /** * Requires the request to be passed in. * * @param request from which to obtain a HTTP session (cannot be null) * @param response not used (can be <code>null</code>) * @param authentication not used (can be <code>null</code>) */ public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { Assert.notNull(request, "HttpServletRequest required"); if (invalidateHttpSession) { HttpSession session = request.getSession(false); if (session != null) { logger.debug("Invalidating session: " + session.getId()); session.invalidate(); } } if (clearAuthentication) { SecurityContext context = SecurityContextHolder.getContext(); context.setAuthentication(null); } SecurityContextHolder.clearContext(); } }
到此這篇關于SpringSecurity自定義Form表單使用方法講解的文章就介紹到這了,更多相關SpringSecurity Form表單內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
IntelliJ IDEA 15款超級牛逼插件推薦(自用,超級牛逼)
這篇文章主要給大家推薦介紹了IntelliJ IDEA 15款超級牛逼插件,這15款插件都是自用的,真的非常推薦,需要的朋友可以參考下2020-11-11Java使用條件語句和循環(huán)結(jié)構(gòu)確定控制流(實例)
下面小編就為大家?guī)硪黄狫ava使用條件語句和循環(huán)結(jié)構(gòu)確定控制流(實例)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06spring中bean id相同引發(fā)故障的分析與解決
最近在工作中遇到了關于bean id相同引發(fā)故障的問題,通過查找相關資料終于解決了,下面這篇文章主要給大家介紹了因為spring中bean id相同引發(fā)故障的分析與解決方法,需要的朋友可以參考借鑒,下面來一起看看吧。2017-09-09Java數(shù)據(jù)結(jié)構(gòu)之KMP算法的實現(xiàn)
這篇文章主要為大家詳細介紹了Java數(shù)據(jù)結(jié)構(gòu)中KMP算法的原理與實現(xiàn),文中的示例代碼講解詳細,對我們學習Java有一定的幫助,需要的可以參考一下2022-11-11