這篇文章主要介紹了SpringSecurity登錄使用JSON格式數據的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨着小編來一起學習學習吧
在使用SpringSecurity中,大夥都知道默認的登錄數據是通過key/value的形式來傳遞的,默認情況下不支持JSON格式的登錄數據,如果有這種需求,就需要自己來解決,本文主要和小夥伴來聊聊這個話題。
基本登錄方案
在說如何使用JSON登錄之前,我們還是先來看看基本的登錄吧,本文爲了簡單,SpringSecurity在使用中就不連接數據庫了,直接在內存中配置用戶名和密碼,具體操作步驟如下:
創建Spring Boot工程
首先創建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配置
創建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("權限不足,訪問失敗"); resp.setStatus(403); resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(new ObjectMapper().writeValueAsString(error)); out.flush(); out.close(); } }); } }
這裏的配置雖然有點長,但是很基礎,配置含義也比較清晰,首先提供BCryptPasswordEncoder作爲PasswordEncoder,可以實現對密碼的自動加密加鹽,非常方便,然後提供了一個名爲zhangsan
的用戶,密碼是123
,角色是user
,最後配置登錄邏輯,所有的請求都需要登錄後才能訪問,登錄接口是/doLogin
,用戶名的key是username,密碼的key是password,同時配置登錄成功、登錄失敗以及註銷成功、權限不足時都給用戶返回JSON提示,另外,這裏雖然配置了登錄頁面爲/login
,實際上這不是一個頁面,而是一段JSON,在LoginController中提供該接口,如下:
@RestController @ResponseBody public class LoginController { @GetMapping("/login") public RespBean login() { return RespBean.error("尚未登錄,請登錄"); } @GetMapping("/hello") public String hello() { return "hello"; } }
這裏/login
只是一個JSON提示,而不是頁面, /hello
則是一個測試接口。
OK,做完上述步驟就可以開始測試了,運行SpringBoot項目,訪問/hello
接口,結果如下:
此時先調用登錄接口進行登錄,如下:
登錄成功後,再去訪問/hello
接口就可以成功訪問了。
使用JSON登錄
上面演示的是一種原始的登錄方案,如果想將用戶名密碼通過JSON的方式進行傳遞,則需要自定義相關過濾器,通過分析源碼我們發現,默認的用戶名密碼提取在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); } //... //... }
從這裏可以看到,默認的用戶名/密碼提取就是通過request中的getParameter來提取的,如果想使用JSON傳遞用戶名密碼,只需要將這個過濾器替換掉即可,自定義過濾器如下:
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類加入進來即可,接下來就可以使用JSON進行登錄了,如下:
好了,本文就先介紹到這裏,有問題歡迎留言討論。 希望對大家的學習有所幫助,也希望大家多多支持神馬文庫。