I have a server with an API. Server is protected by Spring Security.
I want to have an access to the API from an external applications using tokens in request parameters
Firstly user will go to a service which gives him a token and then access the API with this token.
But I want to preserve previous access to the API through standard Spring Security solutions.
So, could you please help me, how can I implement this?
You need to implement custom AuthenticationFilter like this
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);
}
}
and custom SuccessHandler like this
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);
}
}
and wire it in spring config
<?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>
this should help.
You can do it using following bean in spring security TimedKeyBasedPersistenceTokenService
<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>
Apart from that you also need to
use a PreAuthenticatedAuthenticationProvider
and entry-point-ref
attribute of <http>
to Http403ForbiddenEntryPoint
bean
I have found an easier way :
My solution works for token authentication, and form authentication, but you can easily disable one of them if you want.
My filter is similar to Roman's filter, but I don't need the check if the user has access to a particular resource, also no logout handled -> passed to springSecurity.
auth filter :
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;
}
}
then all you have to do to map this filter is configuring it in springSecurity(addFilterBefore), this does not have to be mapped in servlet config.
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 );
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With