2018年4月11日 星期三

Spring Boot + Spring Security + RESTful API的使用方法




最近在使用前後端分離的方式進行開發時,需要引入許可權控制,因為後臺是SpringBoot提供的RESTful API,很自然的想到引入Spring Security。但是遺憾的是Spring Security官網的文件和例子都是傳統的表單登入方式,網上也沒有找到相關文章。不得不自己進行了一番摸索,現將成果進行分享。


寫了一個例子,後端基於SpringBoot構建,僅提供JSON API服務,不提供任何頁面。


基於JSON API的登入,登出操作,基於使用者授權的RESTful API訪問。


當登入成功,登入失敗,登出成功,訪問無許可權API時均返回JSON響應,而不是302跳轉。


可以基於註解獲取前端通過post body提供的引數,如


 @PostMapping(value = "/api/admin/users/id", produces = MEDIA_TYPE) public String editAdminUser( @PathVariable("id") Long id, @JsonArg("$.username") String username, @JsonArg("$.password") String password, @JsonArg("$.enabled") boolean enabled) ...

前端通過create-react-app構建,通過fetch API訪問後端。



 const encode = password ? md5Password(username, password) : "" const result = await this.userManager.update( id, username, password: encode, enabled ) if (result.success) this.setState(...this.state, users: this.replace(id, result)) else Modal.error(title: "Edit User Error", content: result.message) 

首先按照正常的方式引入Maven依賴


 org.springframework.bootspring-boot-starter-parent1.5.9.RELEASEUTF-8UTF-81.8org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-data-jpaorg.springframework.bootspring-boot-starter-securityorg.springframework.bootspring-boot-starter-jdbc

然後重點是SpringSpring的配置



@[email protected](prePostEnabled = true)public class SpringSecurityConfig extends WebSecurityConfigurerAdapter @Autowired private UserDetailsService userDetailsService; @Autowired private CustomLoginHandler customLoginHandler; @Autowired private CustomLogoutHandler customLogoutHandler; @Autowired private CustomAccessDeniedHandler customAccessDeniedHandler; protected void configure(AuthenticationManagerBuilder auth) throws Exception auth.userDetailsService(userDetailsService); protected void configure(HttpSecurity http) throws Exception http.authorizeRequests() .antMatchers("/api/admin/**").hasRole("ADMIN") .antMatchers("/api/basic/**").hasRole("BASIC") .antMatchers("/api/session").permitAll() .antMatchers(HttpMethod.GET).permitAll() .antMatchers("/api/**").hasRole("BASIC"); http.formLogin(); http.logout() .logoutUrl("/api/session/logout") // 登出前呼叫,可用於日誌 .addLogoutHandler(customLogoutHandler) // 登出後呼叫,使用者資訊已不存在 .logoutSuccessHandler(customLogoutHandler); http.exceptionHandling() // 已登入使用者的許可權錯誤 .accessDeniedHandler(customAccessDeniedHandler) // 未登入使用者的許可權錯誤 .authenticationEntryPoint(customAccessDeniedHandler); http.csrf() // 登入API不啟用CSFR檢查 .ignoringAntMatchers("/api/session/**"); // 根據 Header Accept-Language 欄位設定 Locale // 要想啟用錯誤資訊的本地化,還需要設定MessageSource,請參閱Github原始碼 http.addFilterBefore(new AcceptHeaderLocaleFilter(), UsernamePasswordAuthenticationFilter.class); // 替換原先的表單登入 Filter http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); // 繫結 CSRF TOKEN 到響應的 HEADER 上 http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class); private CustomAuthenticationFilter customAuthenticationFilter() throws Exception CustomAuthenticationFilter filter = new CustomAuthenticationFilter(); filter.setAuthenticationSuccessHandler(customLoginHandler); filter.setAuthenticationFailureHandler(customLoginHandler); filter.setAuthenticationManager(authenticationManager()); filter.setFilterProcessesUrl("/api/session/login"); return filter; private static void responseText(HttpServletResponse response, String content) throws IOException response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); byte[] bytes = content.getBytes(StandardCharsets.UTF_8); response.setContentLength(bytes.length); response.getOutputStream().write(bytes); response.flushBuffer(); @Component public static class CustomAccessDeniedHandler extends BaseController implements AuthenticationEntryPoint, AccessDeniedHandler // NoLogged Access Denied @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException responseText(response, errorMessage(authException.getMessage())); // Logged Access Denied @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException responseText(response, errorMessage(accessDeniedException.getMessage())); @Component public static class CustomLoginHandler extends BaseController implements AuthenticationSuccessHandler, AuthenticationFailureHandler // Login Success @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException LOGGER.info("User login successfully, name=", authentication.getName()); responseText(response, objectResult(SessionController.getJSON(authentication))); // Login Failure @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException responseText(response, errorMessage(exception.getMessage())); @Component public static class CustomLogoutHandler extends BaseController implements LogoutHandler, LogoutSuccessHandler // Before Logout @Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) // After Logout @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException responseText(response, objectResult(SessionController.getJSON(null))); private static class AcceptHeaderLocaleFilter implements Filter private AcceptHeaderLocaleResolver localeResolver; private AcceptHeaderLocaleFilter() localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(Locale.US); @Override public void init(FilterConfig filterConfig) @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException Locale locale = localeResolver.resolveLocale((HttpServletRequest) request); LocaleContextHolder.setLocale(locale); chain.doFilter(request, response); @Override public void destroy() 

CustomAuthenticationFilter



public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException UsernamePasswordAuthenticationToken authRequest; try (InputStream is = request.getInputStream()) // 使用JsonPath讀取JSON請求,你也可以換成你喜歡的庫 DocumentContext context = JsonPath.parse(is); String username = context.read("$.username", String.class); String password = context.read("$.password", String.class); authRequest = new UsernamePasswordAuthenticationToken(username, password); catch (IOException e) e.printStackTrace(); authRequest = new UsernamePasswordAuthenticationToken("", ""); setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); 

有討論,纔有進步,大家各抒己見,讓每位同學學到不一樣的!





http://www.kubonews.com/2018041112872.html

生活苦悶?快上酷播亮新聞:http://www.kubonews.com

沒有留言:

張貼留言