I'm new to Spring Security and I'm trying to secure a REST api inside my app. I have the app URIS and then I have an URI like "/api/v1/" to my rest api.
I have secured my application with username/password authentication and it's working fine, but now I want to secure my REST api returning 401 Unauthorized if the user isn't authenticated, but I don't know how to keep the two authentications together.
What it's the way to go here?
PS: I'm using Spring MVC with Spring Security
This is my Spring Security configuration right now:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AccountService accountService;
#Bean
public TokenBasedRememberMeServices rememberMeServices() {
return new TokenBasedRememberMeServices("remember-me-key", accountService);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new StandardPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.eraseCredentials(true).userDetailsService(accountService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/favicon.ico", "/resources/**", "/signup").permitAll().anyRequest()
.authenticated()
.and().formLogin().loginPage("/signin").permitAll().failureUrl("/signin?error=1")
.loginProcessingUrl("/authenticate")
.and().logout().logoutUrl("/logout").permitAll().logoutSuccessUrl("/signin?logout")
.and().rememberMe().rememberMeServices(rememberMeServices()).key("remember-me-key")
.and().csrf();
}
#Bean(name = "authenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
So you want to have form login plus secure rest api right?
jhipster can generate such project structure. Let me give some example code for achieving this if you don't want to use jhipster (it's pretty cool though, I recommend)
to return 401 for unauthorized you need something like this:
/**
* Returns a 401 error code (Unauthorized) to the client.
*/
#Component
public class Http401UnauthorizedEntryPoint implements AuthenticationEntryPoint {
private final Logger log = LoggerFactory.getLogger(Http401UnauthorizedEntryPoint.class);
/**
* Always returns a 401 error code to the client.
*/
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2)
throws IOException,
ServletException {
log.debug("Pre-authenticated entry point called. Rejecting access");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied");
}
}
then inject and add this to your SecurityConfig.configure(HttpSecurity http) method:
http.authenticationEntryPoint(authenticationEntryPoint)
plus since REST requests are ajax requests you also need ajax entry points:
/**
* Returns a 401 error code (Unauthorized) to the client, when Ajax authentication fails.
*/
#Component
public class AjaxAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed");
}
}
/**
* Spring Security success handler, specialized for Ajax requests.
*/
#Component
public class AjaxAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_OK);
}
}
/**
* Spring Security logout handler, specialized for Ajax requests.
*/
#Component
public class AjaxLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler
implements LogoutSuccessHandler {
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_OK);
}
}
add them to security config too:
http.successHandler(ajaxAuthenticationSuccessHandler)
.failureHandler(ajaxAuthenticationFailureHandler)
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
All credit goes to the amazing jhipster authors.
You can use expression based access control
http://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html
Something like,
<intercept-url pattern=/** access="isFullyAuthenticated()/>
Related
Inside a class that extends WebSecurityConfigurerAdapter, I have this authencitation method. I store my users inside an Active Directory.
#Override
public void configure(AuthenticationManagerBuilder auth) {
ActiveDirectoryLdapAuthenticationProvider adProvider
= new ActiveDirectoryLdapAuthenticationProvider("domain.com", "ldap", "ou, dc");
adProvider.setConvertSubErrorCodesToExceptions(true);
adProvider.setUseAuthenticationRequestCredentials(true);
adProvider.setUserDetailsContextMapper(userDetailsContextMapper());
auth.authenticationProvider(adProvider);
}
I used to use a http.formLogin() for testing purpose. Whenever I call localhost:80/security I had to write my username and password in the form.
This method was perfect for testing my connexion to the AD.
But now, I have my frontend in a different server. So here is my question, where should I pass the username and the password to the authentication method ?
How can I hardcode the username and the password and authenicate to my Active Directory ? (for testing only now, after I'm going to use filters and controllers after).
I hope my question is clear.
You have mixed two different questions: How to create outer login page and How to integrate your application with ActiveDirectory.
For single-page applications, your API should send a 200 response along with the user data, or a 4xx response. This can be done by supplying your own handlers, like this (pseudocode just show the idea):
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
...
.successHandler(your authentication success handler object)
.failureHandler(your authentication failure handler object)
.and()
.logout()
...
.logoutSuccessHandler(your logout success handler object)
.and()
.exceptionHandling()
.authenticationEntryPoint(new Http403ForbiddenEntryPoint())
...
}
For example, these are coded as below.
Authentication success handler:
#Component
public class AuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Autowired
private ObjectMapper objectMapper;
#Autowired
private MyService myService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
AbstractUser currentUser = myService.userForClient();
response.getOutputStream().print(
objectMapper.writeValueAsString(currentUser));
clearAuthenticationAttributes(request);
}
}
In summary, it returns a response code 200 with the JSONified current user in the response data.
Authentication failure handler
In fact, there is no need to code a class for the authentication failure handler - the SimpleUrlAuthenticationFailureHandler provided by Spring, if instantiated without any arguments, works as desired.
Logout success handler
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
#Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_OK);
}
}
I'm trying to customize a login process in my own login controller instead of using UsernamePasswordAuthenticationFilter
#PostMapping(value = "/login")
public ResponseEntity<?> login(
HttpServletRequest httpRequest,
#RequestBody AuthenticationRequest authenticationRequest) {
// authentication code here
Authentication authenticate=this.authenticationManager.authenticate(authRequest);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(authentication);
return handlerAuthLogin(httpRequest, result, authorizationRequest);
}
But I can't auto inject Principal in other controllers if I login success as below:
#Controller
public class UsersController {
#RequestMapping(value = "/me")
public string getMyName(Principal principal){
return principal.getName(); // principal is null
}
}
Any guys know why to fix it?
When you execute context.setAuthentication(authentication) the authentication is only valid for the current request. So for the second /me request you need to set the authentication as well.
Therefore you need to authenticate the user on a per-request base. That can be done by implementing a GenericFilterBean:
public class CustomAuthenticationFilter extends GenericFilterBean {
private final AuthenticationManager authenticationManager;
public CustomAuthenticationFilter(
AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
/*
Note that you need to receive the authentication token in different manner now.
Usually headers are used for that.
*/
Authentication authenticate = authenticationManager.authenticate(request.getHeader("authToken"));
SecurityContext context = SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
}
After implementing the filter you need to register it in the servlet container at the position where it is best suited. Spring Security handles the security filters per WebsecutiryConfigurer, so you need to register your filter in the config of the respective configurer of your users.
As an example I put it after ConcurrentSessionFilter:
#Configuration
#Order(1)
public static class UserWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthenticationFilter filter = new PlayerAuthenticationFilter(jwtService,
objectMapper);
http.addFilterAfter(filter, ConcurrentSessionFilter.class);
(...)
}
}
Check out the documentation about filter ordering to find the position best suited for your method.
Update
I wrote a more in-depth blog post about this topic. Fell free to check it out.
#Marcus
Thanks for your clarification, I found the cause for my case.
I wrongly config a
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public WebSecurityConfig() {
super(true); // I disable the default config, so the SecurityContextPersistenceFilter didn't be added by default and all my SecurityContext info is not persistent
}
}
ProviderManager is throwing InternalAuthenticationServiceException.class while retrieving users in DaoAuthenticationProvider.class,
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
I want to handle this exception and return my custom response to the client.
I don't want to handle this by writing custom ProviderManager.
For all other OAuth exceptions i am able to handle the exceptions using Custom WebResponseExceptionTranslator.
But I am unable to catch security exceptions like InternalAuthenticationServiceException.class.
I don't have option to use ErrorController with the /error path, it is breaking other flows.
You can write a class which is annotated with #ControllerAdvice and have a #ExceptionHandler(value=InternalAuthenticationServiceException.class).
Ex:-
#ControllerAdvice
public class ExceptionHandler {
#ExceptionHandler(InternalAuthenticationServiceException.class)
public ResponseEntity<String> handleInternalAuthenticationServiceException(InternalAuthenticationServiceException e) {
ResponseEntity<String> response = new ResponseEntity<String>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
return response;
}
}
UPDATE
If you don't have controllers and using #EnableAuthorizationServer then you need to extend from AuthorizationServerConfigurerAdapter and override configure(AuthorizationServerEndpointsConfigurer endpoints) as below. You can use AuthorizationServerEndpointsConfigurer.exceptionTranslator to handle your InternalAuthenticationServiceException.
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
// other endpoints
.exceptionTranslator(e -> {
if (e instanceof InternalAuthenticationServiceException) {
InternalAuthenticationServiceException internalAuthenticationServiceException = (InternalAuthenticationServiceException) e;
// return a ResponseEntity or throw a custom Exception.
}
});
}
First you need to implements your own AuthenticationEntryPoint the name is not really autoexplicative...
For example if you need to return always status code 200 (only for learning purpose, please donĀ“t do it in real world...)
#Component("myOwnAuthenticationEntryPoint")
public class MyOwnAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, org.springframework.security.core.AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_OK, "Unauthorized");
}
Then in your WebSecurityConfig you need to set it as your authentication exception handler entry point.
...
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
#Autowired
MyOwnAuthenticationEntryPoint myOwnAuthenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(myOwnAuthenticationEntryPoint);
...
}
Thats all. :)
I've solved that problem by override unsuccessfulAuthentication method in my filter and send an error response to the client with the desired HTTP status code. In my case, I also created my custom exception (RecordNotFoundException) that is thrown from my service.
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
if (failed.getCause() instanceof RecordNotFoundException) {
response.sendError((HttpServletResponse.SC_NOT_FOUND), failed.getMessage());
}
}
I'm trying to implement a custom authentication logic with latest version of Spring Boot, Web and Security, but I'm struggling with some issues. I was trying out many solutions in similar questions/tutorials without success or understanding what actually happens.
I'm creating a REST application with stateless authentication, i.e. there is a REST endpoint (/web/auth/login) that expects username and password and returns a string token, which is then used in all the other REST endpoints (/api/**) to identify the user. I need to implement a custom solution as authentication will become more complex in the future and I would like to understand the basics of Spring Security.
To achieve the token authentication, I'm creating a customized filter and provider:
The filter:
public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public TokenAuthenticationFilter() {
super(new AntPathRequestMatcher("/api/**", "GET"));
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String token = request.getParameter("token");
if (token == null || token.length() == 0) {
throw new BadCredentialsException("Missing token");
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(token, null);
return getAuthenticationManager().authenticate(authenticationToken);
}
}
The provider:
#Component
public class TokenAuthenticationProvider implements AuthenticationProvider {
#Autowired
private AuthenticationTokenManager tokenManager;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String token = (String)authentication.getPrincipal();
return tokenManager.getAuthenticationByToken(token);
}
#Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.equals(authentication);
}
}
The config:
#EnableWebSecurity
#Order(1)
public class TokenAuthenticationSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private TokenAuthenticationProvider authProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilterBefore(authenticationFilter(), BasicAuthenticationFilter.class);
}
#Bean
public TokenAuthenticationFilter authenticationFilter() throws Exception {
TokenAuthenticationFilter tokenProcessingFilter = new TokenAuthenticationFilter();
tokenProcessingFilter.setAuthenticationManager(authenticationManager());
return tokenProcessingFilter;
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
}
The AuthenticationTokenManager used in the provider (and also in the login process):
#Component
public class AuthenticationTokenManager {
private Map<String, AuthenticationToken> tokens;
public AuthenticationTokenManager() {
tokens = new HashMap<>();
}
private String generateToken(AuthenticationToken authentication) {
return UUID.randomUUID().toString();
}
public String addAuthentication(AuthenticationToken authentication) {
String token = generateToken(authentication);
tokens.put(token, authentication);
return token;
}
public AuthenticationToken getAuthenticationByToken(String token) {
return tokens.get(token);
}
}
What happens:
I'm appending a valid token in the request to "/api/bla" (which is a REST controller returning some Json). The filter and provider both get invoked. The problem is, the browser is redirected to "/" instead of invoking the REST controller's requested method. This seems to happen in SavedRequestAwareAuthenticationSuccessHandler, but why is this handler being used?
I tried
to implement an empty success handler, resulting in a 200 status code and still not invoking the controller
to do authentication in a simple GenericFilterBean and setting the authentication object via SecurityContextHolder.getContext().setAuthentication(authentication) which results in a "Bad credentials" error page.
I would like to understand why my controller is not being called after I authenticated the token. Besides that, is there a "Spring" way to store the token instead of storing it in a Map, like a custom implementation of SecurityContextRepository?
I really appreciate any hint!
Might be a little late but I was having the same problem and adding:
#Override
protected void successfulAuthentication(
final HttpServletRequest request, final HttpServletResponse response,
final FilterChain chain, final Authentication authResult)
throws IOException, ServletException {
chain.doFilter(request, response);
}
to my AbstractAuthenticationProcessingFilter implementation did the trick.
Use setContinueChainBeforeSuccessfulAuthentication(true) in constructor
I have POST request with something like this in my Spring project:
{"clientKey":"XXX", "accessKey":"ZZZ", ... }
My backend works in very simple paradigm: get clientKey (login) and accessKey (password) params from POST body, check their persistence in DB and then do some business logic.
I need to implement minimal security check logic using Spring Security for each incoming request (without sessions and tokens).
SecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").hasRole("USER")
.and().csrf().disable();
http.addFilterBefore(new ApiAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
ApiAuthorizationFilter.java
public class ApiAuthorizationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//
// always prints "{}", why?
//
Logger.getLogger("test").log(Level.INFO, request.getParameterMap().toString());
//
// Ok, I will make some manual auth operations for testing purposes.
// Seems what it isn't work too..
//
Set<SimpleGrantedAuthority> authorities = new HashSet<>(1);
authorities.add(new SimpleGrantedAuthority("USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(
"94fc97a7b3fd2175472ec4a41bcb3b14",
"746b2aa32fe90f0ba53e6efe7a8d1f1f",
authorities);
SecurityContextHolder.getContext().setAuthentication(auth);
chain.doFilter(request, response);
}
}
What I doing wrong? Does UsernamePasswordAuthenticationFilter work only with login forms when submitted or I need another filter in security chain?