SpringSecurity登入使用JSON格式資料
在使用SpringSecurity中,大夥都知道預設的登入資料是通過key/value的形式來傳遞的,預設情況下不支援JSON格式的登入資料,如果有這種需求,就需要自己來解決,本文主要和小夥伴來聊聊這個話題。
Java通關祕笈小程式,視訊教程、學習資料、重點知識一網打盡,你值得擁有!
基本登入方案
在說如何使用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進行登入了,如下:
好了,本文就先介紹到這裡,有問題歡迎留言討論。
Java通關祕笈小程式,視訊教程、學習資料、重點知識一網打盡,你值得擁有!