我有一个带有 API 的服务器。服务器受 Spring Security 保护。
我想使用请求参数中的令牌从外部应用程序访问 API
首先,用户将转到一个给他一个令牌的服务,然后使用这个令牌访问 API。
但我想通过标准 Spring Security 解决方案保留对 API 的先前访问。
那么,你能帮我吗,我该如何实现呢?
我有一个带有 API 的服务器。服务器受 Spring Security 保护。
我想使用请求参数中的令牌从外部应用程序访问 API
首先,用户将转到一个给他一个令牌的服务,然后使用这个令牌访问 API。
但我想通过标准 Spring Security 解决方案保留对 API 的先前访问。
那么,你能帮我吗,我该如何实现呢?
您需要像这样实现自定义 AuthenticationFilter
public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String SECURITY_TOKEN_KEY = "token";
private static final String SECURITY_TOKEN_HEADER = "X-Token";
private String token = null;
protected CustomAuthenticationFilter() {
super("/");
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
this.token = request.getParameter(SECURITY_TOKEN_KEY);
// or this.token = request.getHeader(SECURITY_TOKEN_HEADER);
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if(request.getParameter(actionParameter) !=null &&
request.getParameter(actionParameter).equals("logout")) {
SecurityContextHolder.clearContext();
return;
}
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
Authentication authResult;
try {
authResult = attemptAuthentication(request, response);
if (authResult == null) {
return;
}
} catch (AuthenticationException failed) {
unsuccessfulAuthentication(request, response, failed);
return;
}
try {
successfulAuthentication(request, response, chain, authResult);
} catch (NestedServletException e) {
if(e.getCause() instanceof AccessDeniedException) {
unsuccessfulAuthentication(request, response, new LockedException("Forbidden"));
}
}
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
AbstractAuthenticationToken userAuthenticationToken = authUserByToken(this.token);
if(userAuthenticationToken == null)
throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
return userAuthenticationToken;
}
private AbstractAuthenticationToken authUserByToken(String tokenRaw) {
AbstractAuthenticationToken authToken = null;
try {
// check your input token, identify the user
// if success create AbstractAuthenticationToken for user to return
// eg:
authToken = new UsernamePasswordAuthenticationToken(username, userHash, userAuthorities);
} catch (Exception e) {
logger.error("Error during authUserByToken", e);
}
return authToken;
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
getSuccessHandler().onAuthenticationSuccess(request, response, authResult);
}
}
和这样的自定义 SuccessHandler
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
return request.getServletPath();
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
request.getRequestDispatcher(request.getServletPath()).forward(request, response);
}
}
并将其连接到弹簧配置中
<?xml version="1.0" encoding="UTF-8"?>
<b:beans
xmlns="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.your.path" />
<aop:aspectj-autoproxy/>
<global-method-security pre-post-annotations="enabled" secured-annotations="enabled" proxy-target-class="true"
access-decision-manager-ref="accessDecisionManager"/>
<http entry-point-ref="restAuthenticationEntryPoint" use-expressions="true"
auto-config="true" access-decision-manager-ref="accessDecisionManager">
<custom-filter ref="restFilter" position="PRE_AUTH_FILTER"/>
<logout/>
</http>
<b:bean id="restAuthenticationEntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
<b:bean id="restFilter" class="com.your.path.CustomAuthenticationFilter">
<b:property name="authenticationSuccessHandler" ref="mySuccessHandler"/>
</b:bean>
<b:bean id="mySuccessHandler" class="com.your.path.CustomAuthenticationSuccessHandler"/>
<b:bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">
<b:property name="allowIfAllAbstainDecisions" value="true"/>
<b:property name="decisionVoters">
<b:list>
<b:bean class="org.springframework.security.access.vote.RoleVoter">
<b:property name="rolePrefix" value=""/>
</b:bean>
<b:bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
</b:list>
</b:property>
</b:bean>
</b:beans>
这应该会有所帮助。
您可以在 Spring Security TimedKeyBasedPersistenceTokenService中使用以下 bean
<bean name="tokenService" class="com.digipos.security.core.token.TimedKeyBasedPersistenceTokenService">
<property name="tokenLifeInMinutes" value="15000"/>
<property name="serverSecret" value="1234567"/>
<property name="serverInteger" value="15062013"/>
<property name="pseudoRandomNumberBits" value="7"/>
<property name="secureRandom" ref="secureRandom"/>
</bean>
<bean name="secureRandom" class="java.security.SecureRandom">
<property name="seed" value="122"/>
</bean>
除此之外,您还需要
用一个PreAuthenticatedAuthenticationProvider
和to beanentry-point-ref
的属性<http>
Http403ForbiddenEntryPoint
我找到了一种更简单的方法:
我的解决方案适用于令牌身份验证和表单身份验证,但如果需要,您可以轻松禁用其中之一。
我的过滤器类似于 Roman 的过滤器,但我不需要检查用户是否可以访问特定资源,也不需要处理注销 -> 传递给 springSecurity。
身份验证过滤器:
public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String SECURITY_TOKEN_KEY = "token";
private static final String SECURITY_TOKEN_HEADER = "X-Token";
public TokenAuthenticationFilter() {
super( "/" );
}
@Override
public void doFilter( ServletRequest req, ServletResponse res, FilterChain chain ) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String token = request.getParameter( SECURITY_TOKEN_KEY );
// or this.token = request.getHeader(SECURITY_TOKEN_HEADER);
if ( token != null ) {
Authentication authResult;
try {
authResult = attemptAuthentication( request, response, token );
if ( authResult == null ) {
notAuthenticated( request, response, new LockedException( "User Not found" ) );
return;
}
} catch ( AuthenticationException failed ) {
notAuthenticated( request, response, failed );
return;
}
try {
successfulAuthentication( request, response, chain, authResult );
return;
} catch ( NestedServletException e ) {
logger.error( e.getMessage( ), e );
if ( e.getCause( ) instanceof AccessDeniedException ) {
notAuthenticated( request, response, new LockedException( "Forbidden" ) );
return;
}
}
}
chain.doFilter( request, response );// return to others spring security filters
}
public void notAuthenticated( HttpServletRequest request, HttpServletResponse response, AuthenticationException failed ) throws IOException {
response.sendRedirect( "http://www.google.ro" );
// unsuccessfulAuthentication( request, response, failed );
}
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response, String token ) throws AuthenticationException, IOException, ServletException {
AbstractAuthenticationToken userAuthenticationToken = authUserByToken( token );
if ( userAuthenticationToken == null )
throw new AuthenticationServiceException( MessageFormat.format( "Error | {0}", "Bad Token" ) );
return userAuthenticationToken;
}
private AbstractAuthenticationToken authUserByToken( String tokenRaw ) {
AbstractAuthenticationToken authToken = null;
try {
// check your input token, identify the user
// if success create AbstractAuthenticationToken for user to return
// eg:
// authToken = new UsernamePasswordAuthenticationToken( username, userHash, userAuthorities );
// authToken = new UsernamePasswordAuthenticationToken( tokenRaw, authToken, )
logger.info( "token received = " + tokenRaw );
// obtain user by your methods
// if ( user != null ) {
// SecurityUser securityUser = new SecurityUser( user );
// return new PreAuthenticatedAuthenticationToken( securityUser, securityUser.getPassword( ), securityUser.getAuthorities( ) );
// }
} catch ( Exception e ) {
logger.error( "Error during authUserByToken", e );
}
return authToken;
}
@Override
protected void successfulAuthentication( HttpServletRequest request, HttpServletResponse response, Authentication authResult ) throws IOException, ServletException {
SecurityContextHolder.getContext( ).setAuthentication( authResult );
new CustomAuthenticationSuccessHandler( ).onAuthenticationSuccess( request, response, authResult );
}
@Override
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException, IOException, ServletException {
logger.error( "No TOKEN PROVIDED" );
return null;
}
}
那么映射此过滤器所需要做的就是在 springSecurity(addFilterBefore) 中配置它,这不必在 servlet 配置中映射。
http.authorizeRequests( ).antMatchers( "/login*" ).permitAll( );
http.authorizeRequests( ).antMatchers( "/register*" ).permitAll( );
http.authorizeRequests( ).antMatchers( "/admin/**" ).hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER" );//
http.authorizeRequests( ).and( ).formLogin( )//
.loginPage( "/login" )//
.successHandler( successHandler( ) )//
.failureUrl( "/login?error" ).permitAll( )//
.and( ).logout( )//
.logoutUrl( "/logout" ).logoutSuccessUrl( "/login?logout" ).permitAll( )//
.and( ).rememberMe( ).key( applicationName + "_key" ).tokenValiditySeconds( 2419200 ); // remember me for 2 weeks
http.addFilterBefore( new TokenAuthenticationFilter( ), AnonymousAuthenticationFilter.class );