SpringSecurity登錄使用JSON格式數(shù)據(jù)的方法
在使用SpringSecurity中,大伙都知道默認(rèn)的登錄數(shù)據(jù)是通過key/value的形式來傳遞的,默認(rèn)情況下不支持JSON格式的登錄數(shù)據(jù),如果有這種需求,就需要自己來解決,本文主要和小伙伴來聊聊這個(gè)話題。
基本登錄方案
在說如何使用JSON登錄之前,我們還是先來看看基本的登錄吧,本文為了簡(jiǎn)單,SpringSecurity在使用中就不連接數(shù)據(jù)庫(kù)了,直接在內(nèi)存中配置用戶名和密碼,具體操作步驟如下:
創(chuàng)建Spring Boot工程
首先創(chuàng)建SpringBoot工程,添加SpringSecurity依賴,如下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
添加Security配置
創(chuàng)建SecurityConfig,完成SpringSecurity的配置,如下:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("zhangsan").password("$2a$10$2O4EwLrrFPEboTfDOtC0F.RpUMk.3q3KvBHRx7XXKUMLBGjOOBs8q").roles("user"); } @Override public void configure(WebSecurity web) throws Exception { } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginProcessingUrl("/doLogin") .successHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException { RespBean ok = RespBean.ok("登錄成功!",authentication.getPrincipal()); resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(new ObjectMapper().writeValueAsString(ok)); out.flush(); out.close(); } }) .failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException { RespBean error = RespBean.error("登錄失敗"); resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(new ObjectMapper().writeValueAsString(error)); out.flush(); out.close(); } }) .loginPage("/login") .permitAll() .and() .logout() .logoutUrl("/logout") .logoutSuccessHandler(new LogoutSuccessHandler() { @Override public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException { RespBean ok = RespBean.ok("注銷成功!"); resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(new ObjectMapper().writeValueAsString(ok)); out.flush(); out.close(); } }) .permitAll() .and() .csrf() .disable() .exceptionHandling() .accessDeniedHandler(new AccessDeniedHandler() { @Override public void handle(HttpServletRequest req, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException { RespBean error = RespBean.error("權(quán)限不足,訪問失敗"); resp.setStatus(403); resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(new ObjectMapper().writeValueAsString(error)); out.flush(); out.close(); } }); } }
這里的配置雖然有點(diǎn)長(zhǎng),但是很基礎(chǔ),配置含義也比較清晰,首先提供BCryptPasswordEncoder作為PasswordEncoder,可以實(shí)現(xiàn)對(duì)密碼的自動(dòng)加密加鹽,非常方便,然后提供了一個(gè)名為zhangsan
的用戶,密碼是123
,角色是user
,最后配置登錄邏輯,所有的請(qǐng)求都需要登錄后才能訪問,登錄接口是/doLogin
,用戶名的key是username,密碼的key是password,同時(shí)配置登錄成功、登錄失敗以及注銷成功、權(quán)限不足時(shí)都給用戶返回JSON提示,另外,這里雖然配置了登錄頁面為/login
,實(shí)際上這不是一個(gè)頁面,而是一段JSON,在LoginController中提供該接口,如下:
@RestController @ResponseBody public class LoginController { @GetMapping("/login") public RespBean login() { return RespBean.error("尚未登錄,請(qǐng)登錄"); } @GetMapping("/hello") public String hello() { return "hello"; } }
這里/login
只是一個(gè)JSON提示,而不是頁面, /hello
則是一個(gè)測(cè)試接口。
OK,做完上述步驟就可以開始測(cè)試了,運(yùn)行SpringBoot項(xiàng)目,訪問/hello
接口,結(jié)果如下:
此時(shí)先調(diào)用登錄接口進(jìn)行登錄,如下:
登錄成功后,再去訪問/hello
接口就可以成功訪問了。
使用JSON登錄
上面演示的是一種原始的登錄方案,如果想將用戶名密碼通過JSON的方式進(jìn)行傳遞,則需要自定義相關(guān)過濾器,通過分析源碼我們發(fā)現(xiàn),默認(rèn)的用戶名密碼提取在UsernamePasswordAuthenticationFilter過濾器中,部分源碼如下:
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY; private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY; private boolean postOnly = true; public UsernamePasswordAuthenticationFilter() { super(new AntPathRequestMatcher("/login", "POST")); } public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } protected String obtainPassword(HttpServletRequest request) { return request.getParameter(passwordParameter); } protected String obtainUsername(HttpServletRequest request) { return request.getParameter(usernameParameter); } //... //... }
從這里可以看到,默認(rèn)的用戶名/密碼提取就是通過request中的getParameter來提取的,如果想使用JSON傳遞用戶名密碼,只需要將這個(gè)過濾器替換掉即可,自定義過濾器如下:
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) { ObjectMapper mapper = new ObjectMapper(); UsernamePasswordAuthenticationToken authRequest = null; try (InputStream is = request.getInputStream()) { Map<String,String> authenticationBean = mapper.readValue(is, Map.class); authRequest = new UsernamePasswordAuthenticationToken( authenticationBean.get("username"), authenticationBean.get("password")); } catch (IOException e) { e.printStackTrace(); authRequest = new UsernamePasswordAuthenticationToken( "", ""); } finally { setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } } else { return super.attemptAuthentication(request, response); } } }
這里只是將用戶名/密碼的獲取方案重新修正下,改為了從JSON中獲取用戶名密碼,然后在SecurityConfig中作出如下修改:
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().authenticated() .and() .formLogin() .and().csrf().disable(); http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } @Bean CustomAuthenticationFilter customAuthenticationFilter() throws Exception { CustomAuthenticationFilter filter = new CustomAuthenticationFilter(); filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException { resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); RespBean respBean = RespBean.ok("登錄成功!"); out.write(new ObjectMapper().writeValueAsString(respBean)); out.flush(); out.close(); } }); filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException { resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); RespBean respBean = RespBean.error("登錄失敗!"); out.write(new ObjectMapper().writeValueAsString(respBean)); out.flush(); out.close(); } }); filter.setAuthenticationManager(authenticationManagerBean()); return filter; }
將自定義的CustomAuthenticationFilter類加入進(jìn)來即可,接下來就可以使用JSON進(jìn)行登錄了,如下:
好了,本文就先介紹到這里,有問題歡迎留言討論。 希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
如何基于spring security實(shí)現(xiàn)在線用戶統(tǒng)計(jì)
這篇文章主要介紹了如何基于spring security實(shí)現(xiàn)在線用戶統(tǒng)計(jì),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06搭建maven私有倉(cāng)庫(kù)的方法實(shí)現(xiàn)
Maven是一個(gè)流行的Java項(xiàng)目管理工具,它可以幫助我們管理項(xiàng)目的構(gòu)建、報(bào)告和文檔,本文主要介紹了搭建maven私有倉(cāng)庫(kù)的方法實(shí)現(xiàn),感興趣的可以了解一下2023-05-05解決Spring?Boot應(yīng)用打包后文件訪問問題
在Spring Boot項(xiàng)目的開發(fā)過程中,一個(gè)常見的挑戰(zhàn)是如何有效地訪問和操作資源文件,本文就來介紹一下解決Spring?Boot應(yīng)用打包后文件訪問問題,感興趣的可以了解一下2024-01-01Java虛擬機(jī)內(nèi)存溢出與內(nèi)存泄漏
這篇文章主要介紹了Java虛擬機(jī)內(nèi)存溢出與內(nèi)存泄漏,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04java實(shí)現(xiàn)上傳圖片尺寸修改和質(zhì)量壓縮
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)上傳圖片尺寸修改和質(zhì)量壓縮,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04必知必會(huì)的SpringBoot實(shí)現(xiàn)熱部署兩種方式
這篇文章主要為大家介紹了必知必會(huì)的SpringBoot實(shí)現(xiàn)熱部署兩種方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Java中隊(duì)列Queue和Deque的區(qū)別與代碼實(shí)例
學(xué)過數(shù)據(jù)結(jié)構(gòu)的,一定對(duì)隊(duì)列不陌生,java也實(shí)現(xiàn)了隊(duì)列,下面這篇文章主要給大家介紹了關(guān)于Java中隊(duì)列Queue和Deque區(qū)別的相關(guān)資料,需要的朋友可以參考下2021-08-08java實(shí)現(xiàn)漢字轉(zhuǎn)unicode與漢字轉(zhuǎn)16進(jìn)制實(shí)例
這篇文章主要介紹了java實(shí)現(xiàn)漢字轉(zhuǎn)unicode與漢字轉(zhuǎn)16進(jìn)制的實(shí)現(xiàn)方法,是Java操作漢字編碼轉(zhuǎn)換的一個(gè)典型應(yīng)用,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2014-10-10