I want to improve my REST API todo application. I want to add to security configuration, when someone logs in I want to redirect him to endpoint with his generated by Utils userId. I want to achive something like that:
.formLogin().defaultSuccessUrl("/users/(logged in our session userId)").permitAll()
you can do it by adding this few things :
in your configure method in WebSecurityConfigurerAdapter add this line :
.formLogin().successHandler(mySuccessHandler())...
add a bean definition by adding
#Bean
public AuthenticationSuccessHandler mySuccessHandler(){
return new MyCustomAuthenticationSuccessHandler();
}
next you need to create the MyCustomAuthenticationSuccessHandler that implements AuthenticationSuccessHandler.
public class MyCustomAuthenticationSuccessHandler
implements AuthenticationSuccessHandler {
protected Log logger = LogFactory.getLog(this.getClass());
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException {
handle(request, response, authentication);
}
protected void handle(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) throws IOException {
String targetUrl = determineYourTargetUrl(request);
if (response.isCommitted()) {
logger.debug(
"Response has already been committed. Unable to redirect to "
+ targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
protected String determineYourTargetUrl(HttpServletRequest request) {
return "users/" + request.getSession().getId();
}
}
Hope that will help you.
Related
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
In my Java Spring application I have implemented OAuth2 user authorization via external OAuth2 provider.
At my localhost in order to authenticate user via this external OAuth2 provider I need to go by the following url: https://127.0.0.1:8443/login/ok and right after OAuth2 dance I can get this user authenticated. So far everything is ok.
But when I have some request parameters in my login url, for example uid and level:
https://127.0.0.1:8443/login/ok?uid=45134132&level=3
after OAuth2 dance I'm redirected to https://127.0.0.1:8443/ and lose those parameters.
In my Chrome network panel I can see following set of calls:
https://127.0.0.1:8443/login/ok?uid=45134132&level=3
https://connect.ok.ru/oauth/authorize?redirect_uri=https://127.0.0.1:8443/login/ok?uid%3D45134132%26level%3D3&response_type=code&state=AKakq....
https://127.0.0.1:8443/login/ok?uid=45134132&level=3&code=....
https://127.0.0.1:8443/
So I'm losing these parameters after step #3.
Is it possible to configure Spring Security + OAuth2 to pass these parameters to step #4 also ?
This is my config(this a solution based on this answer Spring Security - Retaining URL parameters on redirect to login) but it doesn't work(AuthenticationProcessingFilterEntryPoint .commence method is not invoked):
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.headers().frameOptions().disable()
.and().logout()
.and().antMatcher("/**").authorizeRequests()
.antMatchers("/", "/login**", "/index.html", "/home.html").permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(new AuthenticationProcessingFilterEntryPoint("/"))
.and().logout().logoutSuccessUrl("/").permitAll()
.and().csrf().csrfTokenRepository(csrfTokenRepository())
.and().addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
// #formatter:on
}
public class AuthenticationProcessingFilterEntryPoint extends LoginUrlAuthenticationEntryPoint {
public AuthenticationProcessingFilterEntryPoint(String loginFormUrl) {
super(loginFormUrl);
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
redirectStrategy.sendRedirect(request, response, getLoginFormUrl() + "?" + request.getQueryString());
}
}
What can be wrong ?
I have implemented this in the following way:
private Filter ssoFilter(ClientResources client, String path) {
OAuth2ClientAuthenticationProcessingFilter clientFilter = new OAuth2ClientAuthenticationProcessingFilter(path);
.......
clientFilter.setAuthenticationSuccessHandler(new UrlParameterAuthenticationHandler());
return clientFilter;
}
public class UrlParameterAuthenticationHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
String targetUrl = determineTargetUrl(request, response);
if (response.isCommitted()) {
logger.debug("Response has already been committed. Unable to redirect to " + targetUrl);
return;
}
String queryString = HttpUtils.removeParams(request.getQueryString(), "state", "code");
targetUrl = !StringUtils.isEmpty(queryString) ? targetUrl + "?" + queryString : targetUrl;
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
}
Please correct me if there is a better approach
I am trying to secure my Spring Rest API with token here is my custom filter
public class CustomTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final Logger logger = LoggerFactory.getLogger(CustomTokenAuthenticationFilter.class);
public CustomTokenAuthenticationFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultFilterProcessesUrl));
setAuthenticationManager(new NoOpAuthenticationManager());
setAuthenticationSuccessHandler(new TokenSimpleUrlAuthenticationSuccessHandler());
}
public final String HEADER_SECURITY_TOKEN = "X-CustomToken";
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String token = request.getHeader(HEADER_SECURITY_TOKEN);
logger.info("token found:"+token);
AbstractAuthenticationToken userAuthenticationToken = authUserByToken(token);
if(userAuthenticationToken == null || userAuthenticationToken.getPrincipal().equals("guest")) throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
return userAuthenticationToken;
}
/**
* authenticate the user based on token
* #return
*/
private AbstractAuthenticationToken authUserByToken(String token) {
if(token==null) {
return null;
}
AbstractAuthenticationToken authToken = new MyToken(token);
try {
return authToken;
} catch (Exception e) {
logger.error("Authenticate user by token error: ", e);
}
return authToken;
}
#Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
super.doFilter(req, res, chain);
}
}
and here is how I configured it
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
protected AbstractAuthenticationProcessingFilter getFilter() {
return new CustomTokenAuthenticationFilter("/api/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(getFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable();
}
}
If you look at the getFilter(), I have passed "/api/*" as a filter processing url, but I want to configure these urls with HttpSecurity object, some thing as follows
http.authorizeRequests().antMatchers("/", "/rome").permitAll()
.antMatchers("/api/admin", "/api/newUser").access("hasRole('ADMIN')")
.antMatchers("/api/db").access("hasRole('ADMIN') or hasRole('DBA')")
Problem I see is that, the Custom filter requires a String as "filter processing url" but I do not want specify anything. That information should be passed by configuring HttpSecurity object through antMatchers etc.
Is it really possible? if yes how can I achieve that?
I used OncePerRequestFilter.
public class MyAuthenticationFilter extends OncePerRequestFilter {
// private RequestMatcher requestMatcher;
private List<RequestMatcher> includedPathMatchers = new ArrayList<>();
private List<RequestMatcher> excludedPathMatchers = new ArrayList<>();
// implement getters and setters
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// your filter implementation and security logics
}
}
You can treat this class as a normal bean (use #Autowired and so on). Then you just need do register it in your context and inject it in the security chain.
Hope it helps.
This answer will be useful to you. It says to use setter setFilterProcessingURL() available in AbstractAuthenticationProcessingFilter
I know this question can be found with different solutions. But I am unable to get it working in my project.
We are sending mails to users which has link to perform some action in the application. When user click on url he should be redirect to login page if he is not logged in and after login should be navigated to the targeted URL.
I am trying to fix using CustomLoginSuccessHandler here is the code.:
public class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
// public CustomLoginSuccessHandler(String defaultTargetUrl) {
// setDefaultTargetUrl(defaultTargetUrl);
// }
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
HttpSession session = request.getSession(false);
if (session != null) {
String redirectUrl = (String) session.getAttribute("url_prior_login");
if (redirectUrl != null) {
// we do not forget to clean this attribute from session
session.removeAttribute("url_prior_login");
// then we redirect
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
Configurations I am using are :
#Bean
public SavedRequestAwareAuthenticationSuccessHandler authenticationSuccessHandler(){
CustomLoginSuccessHandler successHandler = new CustomLoginSuccessHandler();
// SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
// successHandler.setUseReferer(true); getting NULL in the controller every time
// successHandler.setTargetUrlParameter("targetUrl"); this also doesnt work as browser is redirect to /login page and URL parameters are lost
return successHandler;
}
protected void configure(HttpSecurity http) throws Exception {
http
.logout().logoutUrl("/logout").deleteCookies("JSESSIONID").logoutSuccessUrl("/logoutSuccess")
.and()
.authorizeRequests()
.antMatchers("/privacyPolicy", "/faq", "/aboutus", "/termsofuse", "/feedback","/feedbackSubmit", "/contactSsm", "/resources/**", "/userReply", "/userReplySubmit", "/image", "/logoutExternal", "/closeit").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(authenticationSuccessHandler)
.loginPage("/login")
.defaultSuccessUrl("/")
.permitAll();
// .and().exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint());
}
Problem using this configuration is, If i request for url say 'http:localhost:8080/showPage' spring security is navigating to 'http:localhost:8080/login' and I am unable to capture anything from original URL. Same problem occurs when I try to use a custom variable targetUrl and using it in the same CustomLoginSuccessHandler.
Please let me know if am taking a wrong approach or something else is missing
Also tried using Custom EntryPoint but unable to redirect using my entrypoint.
#Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint{
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
request.getSession().setAttribute("targetUrl",request.getRequestURL());
redirectStrategy.sendRedirect(request,response,request.getRequestURL().toString());
}
}
Controller :
#RequestMapping(value="/login")
public ModelAndView loginHandler(HttpServletRequest request) {
ModelAndView mav = new ModelAndView();
String targetUrl = request.getParameter("targetUrl");
if(targetUrl!=null){ // targetUrl is always null as spring security is navigating to /login asd parameters are lost
request.getSession().setAttribute("url_prior_login",targetUrl);
}
mav.setViewName("login");
return mav;
}
To login, page is navigated to a different domain. and I pass a redirect URL to that domain after successful login it redirects the page back to the redirecturl
<a href="https://domain/sso/identity/login?channel=abc&ru=${externalUrl.applicationUrl}login" >Sign In</a>
Spring Security already stores the request using a RequestCache the default implementation HttpSessionRequestCache stores the last request in the HTTP session. You can access it using the SPRING_SECURITY_SAVED_REQUEST attribute name to get it from the session.
Doing something like this in your controller
public ModelAndView login(HttpServletRequest req, HttpSession session) {
ModelAndView mav = new ModelAndView("login");
if (session != null) {
SavedRequest savedRequest = session.getAttribute("SPRING_SECURITY_SAVED_REQUEST");
if (savedRequest != null) {
mav.addObject("redirectUrl", savedRequest.getRedirectUrl());
}
}
return mav;
}
Then in your JSP you can use the redirectUrl to dynamically construct your URL.
http://your.sso/login?url=${redirectUrl}
The final thing you need to do is to make /login accessible for everyone by adding it to the list which is protected by permitAll(). If you don't do this, you will get into a loop or the last request is overwritten and will always point to the login page.
.antMatchers("/privacyPolicy", "/faq", "/aboutus", "/termsofuse", "/feedback","/feedbackSubmit", "/contactSsm", "/resources/**", "/userReply", "/userReplySubmit", "/image", "/logoutExternal", "/closeit", "/login").permitAll()
You don't need any other custom classes like EntryPoints or AuthenticationSuccessHandler implementations.
However as you are using SSO it would be probably best to investigate a proper integration with the SSO solution instead of this hack with a login page.
You will at least have one problem : HttpSession session = request.getSession();.
getSession()
Returns the current session associated with this request, or if the request does not have a session, creates one.
You should use getSession(false) if you want a null return in case there is no session.
In your case you'll never get a null session.
I had the same issue and have solved it by using SavedRequestAwareAuthenticationSuccessHandler as a successHandler to make Spring handle the saved request that was requested before redirecting to login page when user is not logged.
In WebSecurityConfigurerAdapter:
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String LOGIN_PATH = "/login";
#Autowired
MyApplicationAuthenticationSuccessHandler myApplicationAuthenticationSuccessHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
// Set the default URL when user enters a non internal URL (Like https://my-application.com)
myApplicationAuthenticationSuccessHandler.setDefaultTargetUrl("/myapp/home");
http.authorizeRequests().antMatchers("/resources/**").permitAll().antMatchers(LOGIN_PATH).permitAll().antMatchers("/auto/**").authenticated()
.and().formLogin().loginPage(LOGIN_PATH).permitAll()
.successHandler(myApplicationAuthenticationSuccessHandler).and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl(LOGIN_PATH)
.invalidateHttpSession(true).deleteCookies("JSESSIONID").permitAll().and().sessionManagement().invalidSessionUrl(LOGIN_PATH);
}
}
In custom SavedRequestAwareAuthenticationSuccessHandler:
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
#Component("myApplicationAuthenticationSuccessHandler")
public class MyApplicationAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException {
try {
super.onAuthenticationSuccess(request, response, authentication);
} catch (ServletException e) {
// redirect to default page (home in my case) in case of any possible problem (best solution in my case)
redirectStrategy.sendRedirect(request, response, "/myapp/home");
}
}
}
I'm in the process of setting up Spring Security. My CookieAuthenticationFilter should make sure to keep users out unless they have a cookie with an UUID we accept. Although CookieAuthenticationFilter sets an empty context if the UUID is not accepted I still have access to all URLs.
Any idea what's missing?
This is my security configuration:
#Configuration
#EnableWebMvcSecurity
public class LIRSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilter(cookieAuthenticationFilter())
.authorizeRequests()
.antMatchers("/**").hasAnyAuthority("ALL");
}
#Bean
public CookieAuthenticationFilter cookieAuthenticationFilter() {
return new CookieAuthenticationFilter(cookieService());
}
private CookieService cookieService() {
return new CookieService.Impl();
}
#Bean(name = "springSecurityFilterChain")
public FilterChainProxy getFilterChainProxy() {
SecurityFilterChain chain = new SecurityFilterChain() {
#Override
public boolean matches(HttpServletRequest request) {
// All goes through here
return true;
}
#Override
public List<Filter> getFilters() {
List<Filter> filters = new ArrayList<Filter>();
filters.add(cookieAuthenticationFilter());
return filters;
}
};
return new FilterChainProxy(chain);
}
}
This is the CookieAuthenticationFilter implementation:
public class CookieAuthenticationFilter extends GenericFilterBean {
#Resource
protected AuthenticationService authenticationService;
private CookieService cookieService;
public CookieAuthenticationFilter(CookieService cookieService) {
super();
this.cookieService = cookieService;
}
#Override
public void doFilter(ServletRequest req, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
UUID uuid = cookieService.extractUUID(request.getCookies());
UserInfo userInfo = authenticationService.findBySessionKey(uuid);
SecurityContext securityContext = null;
if (userInfo != null) {
securityContext = new CookieSecurityContext(userInfo);
SecurityContextHolder.setContext(securityContext);
} else {
securityContext = SecurityContextHolder.createEmptyContext();
}
try {
SecurityContextHolder.setContext(securityContext);
chain.doFilter(request, response);
}
finally {
// Free the thread of the context
SecurityContextHolder.clearContext();
}
}
}
The issue here is that you don't want to use GenericFilterBean as it's not actually part of the Spring Security framework, just regular Spring so it's not aware of how to send security-related messages back to the browser or deny access, etc. If you do want to use the GenericFilterBean you'll need to handle the redirect or the 401 response yourself. Alternatively, look into the AbstractPreAuthenticatedProcessingFilter that is part of the Spring Security framework. There is some documentation here: http://docs.spring.io/spring-security/site/docs/3.0.x/reference/preauth.html