Reputation: 1331
I am trying to create an application that will primarily access a REST API using Spring, and am trying to configure the security side. Trying to present the actual structure of the application using this picture:
I have tried to create a basic structure and I think must be making a minor mistake due to which it's not working as expected.
@Configuration
@EnableWebSecurity
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled=true)
public class UserDetailsSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private NSecurityContextHolder securityContextHolder;
@Autowired
private NHttpServletRequestBinder<Authentication> authenticationBinder;
public static final String DEF_USERS_BY_USERNAME_QUERY
= "SELECT user ";
public static final String GROUPS_BY_USERNAME_QUERY =
"SELECT groups by user";
public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY =
"SELECT authorities";
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(getDataSourceFromJndi())
.usersByUsernameQuery(DEF_USERS_BY_USERNAME_QUERY).
authoritiesByUsernameQuery(DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY).
groupAuthoritiesByUsername(GROUPS_BY_USERNAME_QUERY);
}
private DataSource getDataSourceFromJndi() {
try {
DataSource dataSource = (DataSource) new InitialContext().lookup("DS");
return dataSource;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(getDataSourceFromJndi())
.usersByUsernameQuery(DEF_USERS_BY_USERNAME_QUERY).
authoritiesByUsernameQuery(DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY).
groupAuthoritiesByUsername(GROUPS_BY_USERNAME_QUERY);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// The http.formLogin().defaultSuccessUrl("/path/") method is required when using stateless Spring Security
// because the session cannot be used to redirect to the page that was requested while signed out. Unfortunately
// using this configuration method will cause our custom success handler (below) to be overridden with the
// default success handler. So to replicate the defaultSuccessUrl("/path/") configuration we will instead
// correctly configure and delegate to the default success handler.
final SimpleUrlAuthenticationSuccessHandler delegate = new SimpleUrlAuthenticationSuccessHandler();
delegate.setDefaultTargetUrl("/api/");
// Make Spring Security stateless. This means no session will be created by Spring Security, nor will it use any
// previously existing session.
http.sessionManagement().sessionCreationPolicy(STATELESS);
// Disable the CSRF prevention because it requires the session, which of course is not available in a
// stateless application. It also greatly complicates the requirements for the sign in POST request.
http.csrf().disable();
// Viewing any page requires authentication.
http.authorizeRequests().anyRequest().authenticated();
http
.formLogin().loginPage("http://localhost/web/ui/#access/signin")
.permitAll()
// Override the sign in success handler with our stateless implementation. This will update the response
// with any headers and cookies that are required for subsequent authenticated requests.
.successHandler(new NStatelessAuthenticationSuccessHandler(authenticationBinder, delegate));
http.logout().logoutUrl("http://localhost/web/ui/#access/signin").logoutSuccessUrl("http://localhost/web/ui/#access/signin");
// Add our stateless authentication filter before the default sign in filter. The default sign in filter is
// still used for the initial sign in, but if a user is authenticated we need to acknowledge this before it is
// reached.
http.addFilterBefore(
new StatelessAuthenticationFilter(authenticationBinder, securityContextHolder),
UsernamePasswordAuthenticationFilter.class
);
}
}
And I have two types of authenticationBinder i.e. TokenBased and UserNameBased.
TokenBased:
@Component
public class NXAuthTokenHttpServletRequestBinder implements NHttpServletRequestBinder<String> {
private static final String X_AUTH_TOKEN = "X-AUTH-TOKEN";
private final NTokenFactory tokenFactory;
@Autowired
public NXAuthTokenHttpServletRequestBinder(NTokenFactory tokenFactory) {
this.tokenFactory = tokenFactory;
}
@Override
public void add(HttpServletResponse response, String username) {
final String token = tokenFactory.create(username);
response.addHeader(X_AUTH_TOKEN, token);
response.addCookie(new Cookie(X_AUTH_TOKEN, token));
}
@Override
public String retrieve(HttpServletRequest request) {
final String cookieToken = findToken(request);
if (cookieToken != null) {
return tokenFactory.parseUsername(cookieToken);
}
return null;
}
private static String findToken(HttpServletRequest request) {
Enumeration<String> it = request.getHeaderNames();
while(it.hasMoreElements()){
System.out.println(it.nextElement());
}
final String headerToken = request.getHeader(X_AUTH_TOKEN);
if (headerToken != null) {
return headerToken;
}
final Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (X_AUTH_TOKEN.equals(cookie.getName())) {
return cookie.getValue();
}
}
}
return null;
}
}
UserBased:
@Component
@Primary
public class NUserAuthenticationFactory implements NHttpServletRequestBinder<Authentication> {
private final NHttpServletRequestBinder<String> httpServletRequestBinder;
@Autowired
public NUserAuthenticationFactory(NHttpServletRequestBinder<String> httpServletRequestBinder) {
this.httpServletRequestBinder = httpServletRequestBinder;
}
@Override
public void add(HttpServletResponse response, Authentication authentication) {
httpServletRequestBinder.add(response, authentication.getName());
}
@Override
public UserAuthentication retrieve(HttpServletRequest request) {
final String username = httpServletRequestBinder.retrieve(request);
if (username != null) {
return new UserAuthentication(new CustomJDBCDaoImpl().loadUserByUsername(username));
}
return null;
}
}
Problem Whenever I load my application it comes into UserBased Authentication and then try to fetch the username from token instead of validating it from database. But, by that time no token was there as it's the first post request I make from the UI. And it redirects me back to the same Login Page.
Logs:
Fine: / at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter' Fine: / at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter' Fine: / at position 3 of 12 in additional filter chain; firing Filter: 'HeaderWriterFilter' Fine:
Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@a837508 Fine: / at position 4 of 12 in additional filter chain; firing Filter: 'LogoutFilter' Fine: Checking match of request : '/'; against 'http://localhost/web/ui/#access/signin' Fine: / at position 5 of 12 in additional filter chain; firing Filter: 'StatelessAuthenticationFilter' Fine: / at position 6 of 12 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter' Fine: Request 'GET /' doesn't match 'POST http://localhost/web/ui/#access/signin Fine: / at position 7 of 12 in additional filter chain; firing Filter: 'RequestCacheAwareFilter' Fine: / at position 8 of 12 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter' Fine: / at position 9 of 12 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter' Fine: Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS' Fine: / at position 10 of 12 in additional filter chain; firing Filter: 'SessionManagementFilter' Fine: Requested session ID 3e2c15a2a427bf47e51496d2a186 is invalid. Fine: / at position 11 of 12 in additional filter chain; firing Filter: 'ExceptionTranslationFilter' Fine: / at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor' Fine: Secure object: FilterInvocation: URL: /; Attributes: [authenticated] Fine: Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS Fine: Voter: org.springframework.security.web.access.expression.WebExpressionVoter@2ac71565, returned: -1 Fine: Access is denied (user is anonymous); redirecting to authentication entry point org.springframework.security.access.AccessDeniedException: Access is denied
Upvotes: 4
Views: 1999
Reputation: 1019
Step wise solution for your problem can be..
UsernamePasswordAuthenticationFilter
to authenticate the user through Username and password and generate a unique token.AuthenticationProvider
implementation to authenticate the user for successive request.Place custom security filter befor UsernamePasswordAuthenticationFilter
as given below
http.addFilterBefore(CustomTokenBasedAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
register your AuthenticationProvider
implementation with AuthenticationManager
And Thats it!!!
Note:- A better way to secure your rest APIs is to use some standard protocols like oauth1a, oauth2.0 etc. Spring provides a novel implementation of oauth1a and oauth2.0 protocol.
Upvotes: 3