[英]How to secure REST API with Spring Boot and Spring Security?
我知道保護 REST API 是一個廣受評論的話題,但我無法創建一個符合我的標准的小型原型(我需要確認這些標准是現實的)。 如何保護資源以及如何使用 Spring 安全性有很多選擇,我需要澄清我的需求是否切合實際。
我的要求
當前狀態
我的 REST API 運行良好,但現在我需要保護它。 當我在尋找解決方案時,我創建了一個javax.servlet.Filter
過濾器:
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String accessToken = request.getHeader(AUTHORIZATION_TOKEN);
Account account = accountDao.find(accessToken);
if (account == null) {
throw new UnauthorizedException();
}
chain.doFilter(req, res);
}
但是這個帶有javax.servlet.filters
的解決方案不能按我的需要工作,因為通過@ControllerAdvice
和 Spring servlet dispatcher
處理異常存在問題。
我需要的
我想知道這些標准是否現實並獲得任何幫助,如何開始使用 Spring Security 保護 REST API。 我閱讀了許多教程(例如Spring Data REST + Spring Security ),但都在非常基本的配置中工作 - 用戶及其憑據存儲在配置中的內存中,我需要使用 DBMS 並創建自己的身份驗證器。
請給我一些想法如何開始。
基於令牌的身份驗證 - 用戶將提供其憑據並獲得唯一且有時間限制的訪問令牌。 我想在我自己的實現中管理令牌創建、檢查有效性、到期。
實際上,使用過濾器進行令牌身份驗證 - 在這種情況下是最好的方法
最終,您可以通過 Spring Data 創建 CRUD 來管理 Token 的屬性,例如過期等。
這是我的令牌過濾器:http: //pastebin.com/13WWpLq2
和令牌服務實現
一些 REST 資源將是公開的 - 根本不需要進行身份驗證
這不是問題,您可以通過 Spring 安全配置管理您的資源,如下所示: .antMatchers("/rest/blabla/**").permitAll()
某些資源只能由具有管理員權限的用戶訪問,
看看@Secured
類的注釋。 例子:
@Controller
@RequestMapping(value = "/adminservice")
@Secured("ROLE_ADMIN")
public class AdminServiceController {
其他資源在所有用戶授權后都可以訪問。
回到 Spring Security 配置,你可以像這樣配置你的 url:
http
.authorizeRequests()
.antMatchers("/openforall/**").permitAll()
.antMatchers("/alsoopen/**").permitAll()
.anyRequest().authenticated()
我不想使用基本身份驗證
是的,通過令牌過濾器,您的用戶將通過身份驗證。
Java 代碼配置(不是 XML)
回到上面的話,看看@EnableWebSecurity
。 你的班級將是:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {}
您必須覆蓋配置方法。 下面的代碼,僅舉例說明如何配置匹配器。 它來自另一個項目。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/assets/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.usernameParameter("j_username")
.passwordParameter("j_password")
.loginPage("/login")
.defaultSuccessUrl("/", true)
.successHandler(customAuthenticationSuccessHandler)
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.invalidateHttpSession(true)
.logoutSuccessUrl("/")
.deleteCookies("JSESSIONID")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.and()
.csrf();
}
Spring security 對於向 REST URL 提供身份驗證和授權也非常有用。 我們不需要指定任何自定義實現。
首先,您需要在安全配置中指定restAuthenticationEntryPoint 的入口點引用,如下所示。
<security:http pattern="/api/**" entry-point-ref="restAuthenticationEntryPoint" use-expressions="true" auto-config="true" create-session="stateless" >
<security:intercept-url pattern="/api/userList" access="hasRole('ROLE_USER')"/>
<security:intercept-url pattern="/api/managerList" access="hasRole('ROLE_ADMIN')"/>
<security:custom-filter ref="preAuthFilter" position="PRE_AUTH_FILTER"/>
</security:http>
restAuthenticationEntryPoint 的實現可能如下所示。
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException ) throws IOException {
response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" );
}
}
在此之后,您需要指定 RequestHeaderAuthenticationFilter。 它包含 RequestHeader 鍵。 這主要用於識別用戶的身份驗證。 通常 RequestHeader 在進行 REST 調用時會攜帶此信息。 例如考慮下面的代碼
<bean id="preAuthFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
<property name="principalRequestHeader" value="Authorization"/>
<property name="authenticationManager" ref="authenticationManager" />
</bean>
這里,
<property name="principalRequestHeader" value="Authorization"/>
“授權”是呈現傳入請求的密鑰。 它保存所需的用戶身份驗證信息。 您還需要配置 PreAuthenticatedAuthenticationProvider 以滿足我們的要求。
<bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<property name="preAuthenticatedUserDetailsService">
<bean id="userDetailsServiceWrapper"
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<property name="userDetailsService" ref="authenticationService"/>
</bean>
</property>
</bean>
此代碼將通過身份驗證和授權來保護 REST url,而無需任何自定義實現。
有關完整代碼,請找到以下鏈接:
我也搜索了很長時間。我正在做一個類似的項目。我發現Spring有一個模塊可以通過redis實現會話。 它看起來簡單而有用。 我也會添加到我的項目中。 可能會有所幫助:
http://docs.spring.io/spring-session/docs/1.2.1.BUILD-SNAPSHOT/reference/html5/guides/rest.html
http.addFilterBefore()
與自定義過濾器一起使用的另一種方法此解決方案更像是幫助您設置基礎的骨架。 我創建了一個working demo
並添加了一些必要的評論來幫助理解這個過程。 它帶有一些簡單role-based
permission-based
身份驗證/授權,一個可publically accessable endpoint
設置,您可以輕松獲取和使用。
用戶類設置:
public class User implements UserDetails {
private final String username;
private final String password;
private final List<? extends GrantedAuthority> grantedAuthorities;
public User(
String username,
String password,
List<? extends GrantedAuthority> grantedAuthorities
) {
this.username = username;
this.password = password;
this.grantedAuthorities = grantedAuthorities;
}
// And other default method overrides
}
通過addFilterBefore()
方法添加自定義過濾器:
http
.authorizeRequests()
.antMatchers("/")
.permitAll()
.addFilterBefore( // Filter login request only
new LoginFilter("login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class
)
.addFilterBefore( // Filter logout request only
new LogoutFilter("logout"),
UsernamePasswordAuthenticationFilter.class
)
.addFilterBefore( // Verify user on every request
new AuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class
);
自定義LoginFilter
擴展AbstractAuthenticationProcessingFilter
並覆蓋三個方法來處理身份驗證:
public class LoginFilter extends AbstractAuthenticationProcessingFilter {
public LoginFilter(String url, AuthenticationManager authManager) {
super(url, authManager);
}
@Override
public Authentication attemptAuthentication(
HttpServletRequest req,
HttpServletResponse res
)
throws AuthenticationException, IOException {
LoginUserDto loginUserDto = new ObjectMapper() // this dto is a simple {username, password} object
.readValue(req.getInputStream(), LoginUserDto.class);
return getAuthenticationManager()
.authenticate(
new UsernamePasswordAuthenticationToken(
loginUserDto.getUsername(),
loginUserDto.getPassword()
)
);
}
@Override
protected void successfulAuthentication(
HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth
)
throws IOException, ServletException {
User user = (User) auth.getPrincipal();
req.getSession().setAttribute(UserSessionKey, user); // Simply put it in session
res.getOutputStream().print("You are logged in as " + user.getUsername());
}
@Override
protected void unsuccessfulAuthentication(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException failed
)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("text/plain");
response.getOutputStream().print(failed.getMessage());
}
}
自定義AuthenticationFilter
檢查存儲在會話中的auth info
並傳遞給SecurityContext
:
public class AuthenticationFilter extends GenericFilterBean {
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain filterChain
)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpSession session = req.getSession();
User user = (User) session.getAttribute(UserSessionKey);
if (user != null) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
user,
user.getPassword(),
user.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authToken);
}
// Either securityContext has authToken or not, we continue the filter chain
filterChain.doFilter(request, response);
}
}
自定義LogoutFilter
相當簡單直接,使會話無效並終止身份驗證過程:
public class LogoutFilter extends AbstractAuthenticationProcessingFilter {
public LogoutFilter(String url) {
super(url);
}
@Override
public Authentication attemptAuthentication(
HttpServletRequest req,
HttpServletResponse res
)
throws AuthenticationException, IOException {
req.getSession().invalidate();
res.getWriter().println("You logged out!");
return null;
}
}
這三個自定義過濾器所做的是, login
和logout
過濾器只監聽它們各自的端點。
在登錄過濾器中,我們從客戶端獲取username and password
,並根據數據庫(在現實世界中)檢查它以進行驗證,如果它是有效用戶,則將其放入會話並將其傳遞給SecurityContext
。
在注銷過濾器中,我們只是invalidate the session
並返回一個字符串。
雖然自定義AuthenticationFilter
將對每個傳入請求進行身份驗證,以嘗試從會話中獲取用戶信息,然后將其傳遞給SecurityContext
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.