Extending AjaxAwareAuthenticationSuccessHandler, Remembering destination URL - grails

Grails 1.3.7
Spring-Security-Core 1.1.2
I've implemented a custom class that extends AjaxAwareAuthenticationSuccessHandler so that specific roles can be taken to specific URL's after logging in which works great. However, if the session expires I need to be able to take the user to the requested URL when the session expired, overriding the Role based URL.
Here is a simplified version of my code
class MyAuthSuccessHandler extends AjaxAwareAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response,
final Authentication authentication) throws ServletException, IOException {
def goAdmin = false
authentication.authorities.each { ga ->
if (ga.authority.equals('ROLE_ADMIN')) {
goAdmin = true
}
}
if (goAdmin) {
response.sendRedirect(request.contextPath + '/admin/index')
}else{
super.onAuthenticationSuccess(request, response, authentication)
}
}
}
I tried adding a call to determineTargetUrl(request, response) but it always returns '/' even though I've requested a resource like /admin/foo which is protected.
Thanks.

Requesting
super.determineTargetUrl(request, response);
should work, if you use SavedRequestAwareAuthenticationSuccessHandler as super class. I am not sure if it is possible for you to switch to this class in your scenario. Maybe this can help, but I guess you are fully aware of it: http://omarello.com/2011/09/grails-custom-target-urls-after-login/

Related

Wanting a custom ExpressionInterceptURL to dynamically manage access to URLs

We have information packs at URLs which need a user right to access, laid out as:
/InfoPacks/InfoPack1/
/InfoPacks/InfoPack2/
etc
A user need ROLE_INFOPACK1 to access the /InfoPacks/InfoPack1/ and ROLE_INFOPACK2 to access the /InfoPacks/InfoPack2/ etc.
We are adding packs all the time so putting adding to WebSecurityConfig() with
.antMatchers("/InfoPacks/InfoPack1/**/*").hasAuthority("ROLE_INFOPACK1)
isnt really a goer as it would imply modifying and re-deploying every time a new pack was created while the configure method in security config got larger and larger.
A custom evaluator would be better. eg something that could call a service with
like:
hasPermission(Authentication auth, String targetURL) {
// search auth.GrantAuthorities for a match to targetURL
}
I see this sort of custom permission expression examples for use with PreAuthorize but doesnt seem to be way to do this with URL authorizeRequests(). (at least in version 4).
Any pointers would be very welcome.
Got it figured out, thanks to http://www.baeldung.com/spring-security-custom-voter.
My web security config now look like:
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
.frameOptions().sameOrigin()
.and()
.csrf().disable()
.exceptionHandling()
.and()
.authorizeRequests()
....
.antMatchers("/Infopacks/**/*").authenticated().accessDecisionManager(accessDecisionManager())
..... etc
}
#SuppressWarnings("unchecked")
#Bean
public AccessDecisionManager accessDecisionManager() {
System.out.println("Arrive AccessDecisionManager");
List<AccessDecisionVoter<? extends Object>> decisionVoters
= Arrays.asList(
new WebExpressionVoter(),
new RoleVoter(),
new AuthenticatedVoter(),
new DynamicVoter());
return new UnanimousBased(decisionVoters);
}
The vote method of DynamicVoter looks (in ugly first test - I know it needs work to plug holes) like:
#Override
public int vote(Authentication a, Object s, Collection clctn) {
String url = ((FilterInvocation) s).getRequestUrl();
int vote = ACCESS_ABSTAIN;
if (url.contains("/Infopack")) {
vote = ACCESS_DENIED;
for (GrantedAuthority ga:a.getAuthorities()) {
if (url.toUpperCase().contains(ga.getAuthority()) ) {
vote = ACCESS_GRANTED;
break;
}
}
}
return vote;
}
The user authorization system thus just needs to add the infopack name as a granted authority to allow user access to the infopack directory. You can add new infopack directories at will without changing the code.

Spring Security 4 intercept anonymous user AccessDeniedException

My website allows unauthenticated (anonymous) users to perform a search of content which requires authentication to view. Currently, when they click on a link to view the details of one of the content items on the search results page Spring Security correctly identifies the user as unauthenticated and displays the login page. However, I would like to intervene and instead display a page to encourage the anonymous user to sign up for the website. I have traced what is happening in the filter chain but it's not clear to me whether I should extend an existing filter or handler or create a custom filter or handler. If it's the later I'm not sure where it should go.
When I run this through debug I can see the following happening:
ExceptionTranslationFilter.doFilter executes FilterSecurityInterceptor which determines that the detail page requires authentication (returns a -1 vote and throws an AccessDeniedException)
ExceptionTranslationFilter catches the exception, determines the user is anonymous and calls the authenticationEntryPoint, in this case LoginUrlAuthenticationEntryPoint
LoginUrlAuthenticationEntryPoint invokes the DefaultRedirectStrategy which redirects to the login page
So, basically I need to override the redirection to the login page for this one use case. My best guess is to create a custom filter that checks for the combination of an anonymous user accessing this specific detail page and forces a redirect to the join up page, inserting the filter in the chain after ExceptionTranslationFilter. Or is this total overkill for handling a single page redirect and there's an easier way to accomplish this?
For anyone interested, here's the code for the custom auth entry point, borrowing from LoginUrlAuthenticationEntryPoint and ExceptionTranslationFilter.
public class CustomAuthLoginEntryPoint extends LoginUrlAuthenticationEntryPoint {
private final Logger logger = LoggerFactory.getLogger(getClass());
private PortResolver portResolver = new PortResolverImpl();
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private RequestCache requestCache = new HttpSessionRequestCache();
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private String joinPageUrl;
public CustomAuthLoginEntryPoint(String loginFormUrl) {
super(loginFormUrl);
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
logger.debug("commence");
String redirectUrl = null;
if (!StringUtils.isBlank(joinPageUrl)) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || trustResolver.isAnonymous(auth)) {
SavedRequest savedRequest = requestCache.getRequest(request, response);
redirectUrl = savedRequest.getRedirectUrl();
if (redirectUrl.indexOf("viewDetail") > 0) {
String joinPageUrl = buildRedirectUrlToJoinPage(request);
logger.debug("Redirecting to '" + joinPageUrl + "'");
redirectStrategy.sendRedirect(request, response, joinPageUrl);
return;
}
}
}
super.commence(request, response, authException);
}
protected String buildRedirectUrlToJoinPage(HttpServletRequest request) {
int serverPort = portResolver.getServerPort(request);
String scheme = request.getScheme();
RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder();
urlBuilder.setScheme(scheme);
urlBuilder.setServerName(request.getServerName());
urlBuilder.setPort(serverPort);
urlBuilder.setContextPath(request.getContextPath());
urlBuilder.setPathInfo(joinPageUrl);
return urlBuilder.getUrl();
}
public void setJoinPage(String joinPageUrl) {
this.joinPageUrl = joinPageUrl;
}
}
I added this to my WebSecurityConfigurerAdapter:
#Bean
public CustomAuthLoginEntryPoint customAuthLoginEntryPoint() {
CustomAuthLoginEntryPoint entryPoint = new CustomAuthLoginEntryPoint("/user/login");
entryPoint.setJoinPage("/user/join");
return entryPoint;
}
and the http configure:
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.authenticationEntryPoint(customAuthLoginEntryPoint())

When logout from Spring Security [Boot] and then again if I try to login it's logging in without password. How Can I prevent this

I am using Spring security in my application and configuration is mentioned below.
When User is logged out from system and click in again login then I want to display login page.
#Component
#EnableOAuth2Sso
public static class LoginConfigurer extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
RequestMatcher csrfRequestMatcher = new RequestMatcher() {
// Enabled CSFR protection on the following urls
private AntPathRequestMatcher[] requestMatchers = { new AntPathRequestMatcher("/dashboard/logout") };
#Override
public boolean matches(HttpServletRequest request) {
// If the request match one url the CSFR protection will be
// enabled
for (AntPathRequestMatcher rm : requestMatchers) {
if (rm.matches(request)) {
return true;
}
}
return false;
} // method matches
};
http.csrf().requireCsrfProtectionMatcher(csrfRequestMatcher).csrfTokenRepository(csrfTokenRepository())
.and().antMatcher("/dashboard/**").authorizeRequests().anyRequest()
.hasAnyRole("AUTHENTICATED_USER", "ANONYMOUS").and().sessionManagement().sessionFixation()
.migrateSession().and()
// .csrfTokenRepository(csrfTokenRepository()).and()
// .addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
.logout().invalidateHttpSession(true).logoutUrl("/dashboard/logout").deleteCookies(new String[]{"XSRF-TOKEN","JSESSIONID","remember-me"})
.logoutSuccessUrl("/").permitAll().and().rememberMe().and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
The case you mentioned mostly happens if you use a cookie for authentication as a token and the deleteCookie method is not deleting your authentication cookies correctly.
The best way to identify this is follow :
1.Clear all cookies and login and then observe the cookie name and value and path in inspect element of web browser.
2.Logout and then observe the inspect element request and response with cookies.
3.Observe the path of the cookie carefully.
It is important to observe the path of the cookie is because of the path of the cookie at its creation time is different than its deletion time,it will not get deleted.It has to be exactly the same.
When you call .deleteCookies(...),then internally it calls the CookieClearingLogoutHandler(spring boot security internal class) mentioned as below :
public final class CookieClearingLogoutHandler implements LogoutHandler {
private final List<String> cookiesToClear;
public CookieClearingLogoutHandler(String... cookiesToClear) {
Assert.notNull(cookiesToClear, "List of cookies cannot be null");
this.cookiesToClear = Arrays.asList(cookiesToClear);
}
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
for (String cookieName : cookiesToClear) {
Cookie cookie = new Cookie(cookieName, null);
String cookiePath = request.getContextPath();
if(!StringUtils.hasLength(cookiePath)) {
cookiePath = "/";
}
cookie.setPath(cookiePath);
cookie.setMaxAge(0);
response.addCookie(cookie);
}
}
}
Observe the cookie setPath, you would need to set the cookie path for the cookie you are using for authentication in the same way mentioned in above code.
Hope this helps.

Spring security multiple logout urls?

I am using Spring security java config and I wanted to know a way to implemented log-out for multiple urls. i.e.
logout().logoutRequestMatcher(new
AntPathRequestMatcher("/invalidate")).logoutUrl("/logout");
In this code the normal logout url "/logout" works fine and its a post request but i also want the user to logout for the url "/invalidate" which doesn't seem to work.
According to Spring Security tutorial, it seems that the next is more elegant approach:
In the security form-login tag just add something like this:
<security:logout logout-url="/logout" success-handler-ref="logoutHandler"/>
Every time that you'll hit /logout URL the logoutHandler will be invoked, and on it, you can decide how to behave after a successful logout.
From Spring docs:
All you need to do is to create a new class that implements the interface marked in the image and implement its single method.
On that method decide how to act after a successful logout. for example:
#Component("logoutHandler")
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
#Override
public void onLogoutSuccess(HttpServletRequest request,HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if(request.getParameter("msgShow") != null && request.getParameter("msgShow").equals("false")){
redirectResponse(request, response, "http://" + request.getServerName() + ":" + request.getServerPort() + "/my_web_app/home?logout=false");
}
else{
redirectResponse(request, response,"http://" + request.getServerName() + ":" + request.getServerPort() + "/my_web_app/home?logout=true");
}
}
private void redirectResponse(HttpServletRequest request, HttpServletResponse response, String destination) {
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
response.setHeader("Location", destination);
}
}
Now don't forget to add a #Component annotation to the above logout handler + on security configuration file add the next 2 statements:
<context:annotation-config />
<context:component-scan base-package="package.to.logout.handler" />
This might not be the most elegant way, but you can just specify a #Controller that is mapped to all the URLs you want for logout, e. g.
#Controller
public class LogoutController {
final String logoutRedirectUrl = "redirect:http://yourredirect.xy";
#RequestMapping("/logout")
public String logout1(HttpServletRequest request) throws ServletException {
request.logout();
return logoutRedirectUrl;
}
#RequestMapping("/second/logout/")
public String logout2(HttpServletRequest request) throws ServletException {
request.logout();
return logoutRedirectUrl;
}
}
Just a quick tip, the actual matching should be improved.
List logoutUrls = Arrays.asList(
"/rest/logout1",
"/rest/logout2"
);
RequestMatcher rm = new RequestMatcher() {
#Override
public boolean matches(HttpServletRequest request) {
String uriStr = request.getRequestURI().toString();
return logoutUrls.stream()
.filter(lu -> uriStr.contains(lu))
.findFirst()
.isPresent();
}
};
and then register the request matcher:
http.logout(logout -> logout.logoutRequestMatcher(rm));

Grails - Acegi, how to redirect user to a different domain during authentication?

I have inherited a Grails app, which uses the Acegi 0.5.3 plugin.
The application can be accessed via two completely different URLs e.g., app.domainone.com and app.domaintwo.com. The domain names map to two different user communities. Now I have been tasked with restricting user access from only the domain that they are related to. At the moment the users can visit any of the domains and login to the application.
I have some clue about how Acegi works but, can't say I understand all of it yet. So wanted to ask how I would be able to achieve this.
In an ideal scenario, when the user tries to login, I would like to redirect (if required) to their 'relevant' domain and automagically sign them in with their given credentials. However, as an interim solution even a plain redirect to the relevant login page would suffice.
Here goes my CustomAuthenticationProcessingFilter. There is probably a better solution out there but this helped me with the little knowledge that I have of Grails and Spring Security.
class CustomAuthenticationProcessingFilter extends GrailsAuthenticationProcessingFilter implements
InitializingBean {
//def authenticationManager
#Override
public int getOrder() {
return FilterChainOrder.AUTHENTICATION_PROCESSING_FILTER
}
#Override
void doFilterHttp(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) throws IOException,
ServletException {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
def loginUrl = "${request.getRequestURL().toString() - request.getRequestURI().toString()}${request.contextPath}"
def username = request.getParameter("j_username")
def password = request.getParameter("j_password")
if ( loginUrl && username && password) {
def user = User.findByEmailOrCompanyEmail(username,username)
if(user) {
def query = """select c from Community c, UserCommunity uc
where c.id = uc.comm.id
and uc.user.id = :userId"""
def comm = Community.executeQuery(query,[userId:user.id])
comm = comm?(comm?.get(0)):null
if(loginUrl!=comm?.url) {
println "Trying to login using the wrong URL"
response.sendRedirect(comm.url+'/login/auth')
return
}
}
}
}
//Resume the normal flow
super.doFilterHttp(request, response, chain)
}
#Override
public void afterPropertiesSet() throws Exception {
// TODO Auto-generated method stub
}
}

Resources