在使用SpringSecurity中,大夥都知道默認的登陸數據是經過key/value的形式來傳遞的,默認狀況下不支持JSON格式的登陸數據,若是有這種需求,就須要本身來解決,本文主要和小夥伴來聊聊這個話題。 web
Java通關祕笈小程序,視頻教程、學習資料、重點知識一網打盡,你值得擁有!
spring
在說如何使用JSON登陸以前,咱們仍是先來看看基本的登陸吧,本文爲了簡單,SpringSecurity在使用中就不鏈接數據庫了,直接在內存中配置用戶名和密碼,具體操做步驟以下:數據庫
首先建立SpringBoot工程,添加SpringSecurity依賴,以下:json
<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>
建立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中提供該接口,以下:app
@RestController @ResponseBody public class LoginController { @GetMapping("/login") public RespBean login() { return RespBean.error("還沒有登陸,請登陸"); } @GetMapping("/hello") public String hello() { return "hello"; } }
這裏/login
只是一個JSON提示,而不是頁面, /hello
則是一個測試接口。 ide
OK,作完上述步驟就能夠開始測試了,運行SpringBoot項目,訪問/hello
接口,結果以下: spring-boot
此時先調用登陸接口進行登陸,以下: post
登陸成功後,再去訪問/hello
接口就能夠成功訪問了。學習
上面演示的是一種原始的登陸方案,若是想將用戶名密碼經過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通關祕笈小程序,視頻教程、學習資料、重點知識一網打盡,你值得擁有!