提问人:jnemecz 提问时间:9/13/2015 最后编辑:jatin_ghataliyajnemecz 更新时间:7/17/2022 访问量:119573
如何使用Spring Boot和Spring Security保护REST API?
How to secure REST API with Spring Boot and Spring Security?
问:
我知道保护 REST API 是被广泛评论的话题,但我无法创建一个符合我的标准的小型原型(我需要确认这些标准是现实的)。有很多选项可以保护资源以及如何使用 Spring 安全性,我需要澄清我的需求是否切合实际。
我的要求
- 基于令牌的身份验证器 - 用户将提供其凭据并获得唯一且有时间限制的访问令牌。我想在我自己的实现中管理令牌创建、检查有效性和过期时间。
- 一些 REST 资源将是公开的 - 根本不需要身份验证,
- 某些资源只能由具有管理员权限的用户访问,
- 所有用户在授权后都可以访问其他资源。
- 我不想使用基本身份验证
- Java 代码配置(非 XML)
现状
我的 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);
}
但是这个解决方案不能满足我的需要,因为通过 Spring 处理异常存在问题。javax.servlet.filters
@ControllerAdvice
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 {}
您必须覆盖 configure 方法。下面的代码,仅举例说明如何配置匹配器。它来自另一个项目。
@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 有一个模块可以通过 redis 实现会话。它看起来简单而有用。 我也会添加到我的项目中。可能会有所帮助:
http://docs.spring.io/spring-session/docs/1.2.1.BUILD-SNAPSHOT/reference/html5/guides/rest.html
Spring 安全性对于向 REST URL 提供身份验证和授权也非常有用。我们不需要指定任何自定义实现。
首先,您需要在安全配置中将 entry-point-ref 指定为 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,而无需任何自定义实现。
有关完整代码,请找到以下链接:
https://github.com/srinivas1918/spring-rest-security
要验证 REST API,有 2 种方法
1 - 使用在 application.properties 文件中设置的默认用户名和密码进行基本身份验证
2 - 使用数据库 (userDetailsService) 使用实际用户名和密码进行身份验证
评论
与自定义过滤器一起使用的另一种方式http.addFilterBefore()
此解决方案更像是一个框架,可帮助您设置基础知识。
我创建了一个并添加了一些必要的注释来帮助理解该过程。它带有一些简单的身份验证/授权,您可以轻松拿起和使用这些设置。working demo
role-based
permission-based
publically accessable endpoint
因此,最好查看完整的代码,并在运行应用程序: github 存储库
用户类设置:
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
评论