I have implemented a login page with Java and Spring mvc.
"index.html"
THe problem now is that you can easily get around it by just calling /person.html and you´re into the application.
How can I prevent this without having to use Spring Security?
Very basic answer.
Create an interceptor class that extends HandlerInterceptorAdapter. In the preHandle method check if the user exists in session and return true only if the user exists in session.
public class AuthenticationCheckInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//Check if user exists in session.
//If no, redirect to login page using response.sendRedirect() and return false
//If yes, return true
}
}
Apply this interceptor to all URLs which require you to be logged-in.
A simple example is at http://www.mkyong.com/spring-mvc/spring-mvc-handler-interceptors-example/
Related
i have a PreAuthenticatedProcessingFilter with a custom AuthenticationManager where i do my authentication and create a AuthenticationToken. I now need to access a path variable (eg. id of "/foo/{id}") and use that for my authentication. How can i access the variable? If i use .antMatchers("/foo/{id}").access("#demo.check(authentication,#id)"); for example i cant create my own token.
my current code is:
MyAuthFilter filter = MyAuthFilter();
filter.setAuthenticationManager(new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// ... authentication stuff
// here i want to access the path variable
return new MyAuthenticationToken(foo);
}
});
httpSecurity.antMatcher("/foo/**").csrf().disable().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilter(filter).authorizeRequests().anyRequest().authenticated();
Update
i am now checking everything inside the access expression (you can access the HttpServletRequest there and have the path variables as parameter). I did not want to have logic in the controller or check the raw path. So this works fine for me now:
httpSecurity.antMatcher("/foo/**").csrf().disable().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
.antMatchers("/foo/test/{testId}/**")
.access("#fooApiGuard.check(authentication, #testId)");
#Service
public class FooApiGuard {
#Autowired
private HttpServletRequest request;
public boolean check(Authentication authentication, Long testId) throws AuthenticationException {
// check stuff
return true;
}
}
Spring Security is built as a Filter chain, which means that inside your custom filter or AuthenticationManager you do not have quite the same context as inside the controller method itself. In fact, your custom filter is supposed to augment the context which will be used down the line by your controller.
What you do have access to is the ServletRequest and ServletResponse objects, so if you must you could extract the raw path from that. However, that doesn't give you the nicely separated out request parameter.
If the path parameter is only necessary to determine whether or not someone is authorized then you could simplify your authentication logic and then subsequently augment your controller with additional security checks to validate e.g. domain level security concerns (does the resource belong to the current user).
TLDR: My method requires 2 redirects/forwards to work (1 for authentication and 1 to serve the jsp page). How can I resolve both redirects/forwards (or make it a non-requirement) so as to not run into the error, java.lang.IllegalStateException: Cannot forward after response has been committed.
For more context:
I have a java servlet with a method that looks something like the following:
#GET
#Path("/test")
#Authenticate
public Viewable test(#Context HttpServletRequest request, #Context HttpServletResponse response) {
Map<String, Object> model = createModel();
return new Viewable("/somePath/jspFile", model);
}
The #Authenticate annotation intercepts the call to do some Open ID Connect type authentication which results in the user being forwarded to a different server for all authentication needs. If the user is authenticated, they are redirected back to my application.
However, when hitting the url for this method, I am getting java.lang.IllegalStateException: Cannot forward after response has been committed. I don't know too much about using this Viewable class, but based on the fact that I don't run into that error when returning String/void/whatever else, I assume returning a new Viewable needs to do some forwarding that results in the user seeing the jsp page.
I've read the main SO post about this error, but I am unsure how to apply the fixes to my current problem. For example, I don't know how I would apply something like the following fix:
protected void doPost() {
if (someCondition) {
sendRedirect();
} else {
forward();
}
}
The fix assumes that I can I can either redirect OR forward, but my current method needs a redirect for authentication AND a forward/redirect to serve the jsp page. Maybe there's an obvious fix I'm missing that doesn't require a complete rehaul of the current code?
Edit: It would be nice if I could check if the user was authenticated first, but I assume using this annotation at all automatically entails an initial redirect
Edit: It looks like the user is redirected for the initial login authentication, but does not need to be redirected again after being authenticated once due to SSO
Ok based on some preliminary testing, it seems like the following solution has worked for me:
Check if the user has already been authenticated
Return a Response rather than a Viewable.
Since the user only needs to be redirected the first time for authentication, I can return an empty/meaningless response as a placeholder. And then once the user has been authenticated and is returned to my app, I can return a Viewable wrapped in a Response object.
So the code would look something like the following:
#GET
#Path("/test")
#Authenticate
public Response test(#Context HttpServletRequest request, #Context HttpServletResponse
response) {
Map<String, Object> model = createModel();
if (userIsAuthenticated()) {
return Response.status(401).build();
} else {
return Response.ok(new Viewable("/somePath/jspFile", model)).build();
}
}
I've been trying to implement OAuth2 password expiration filter and I'm unsure about what the proper way would be to do so. The idea is as follows:
User tries to login.
User gets response with a header containing token if the password is expired.
User get's redirected to password change page using that token (i.e. /password-change/{token}).
He submits his old and new passwords, it gets changed.
Some rest controller retrieves user id by that token and does the rest password changing logic.
User should be redirected back to the initial login page where he logins with his new password (if he would be logged in instantly after the password change, he could navigate through secured pages even if the password would not be changed in background due to some exception, etc.).
So... I set a custom flag in user details for password expiration because I can't use credentialsNonExpired as it gets validated in DaoAuthenticationProvider and thrown as an exception which gets processed as InvalidGrantException which doesn't give me much control. I've figured out that in order to access user details right after it's authentication my filter should be in the inner Spring Security filter chain placed after OAuth2AuthenticationProcessingFilter:
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
...
http.addFilterAfter(new PasswordExpirationFilter(), BasicAuthenticationFilter.class
}
}
Why does my filter get placed after OAuth2AuthenticationProcessingFilter while there's no BasicAuthenticationFilter in the chain? I've digged through Spring Security and OAuth2 documentation and sources and couldn't find the right answer.
If that user's password is expired my filter generates some random string and it saves it to retrieve user details later during the password change request (at least it should be):
public class PasswordExpirationFilter extends OncePerRequestFilter implements Filter, InitializingBean {
private static final String TOKEN_HEADER = ...;
private ExpiredPasswordRepository repo; // gets set in a constructor and is basically holding a concurrent map of tokens
...
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
UserDetails details = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (details.isPasswordExpired) {
String uuid = UUID.randomUUID().toString();
repo.push(uuid, details.getId());
SecurityContextHolder.clearContext();
SecurityContextHolder.getContext().setAuthentication(null);
request.getSession(false).invalidate(); // don't create a new session
response.addHeader(TOKEN_HEADER, uuid);
response.sendError(HttpStatus.SC_PRECONDITION_FAILED, "Credentials have expired");
} else {
filterChain.doFilter(request, response);
}
}
}
Do I have to revoke the OAuth token as well? It gets reused in later requests and I keep getting the last userDetails object and therefore I keep getting the same response from my filter.
Is it even the right place to do all this validation? How should one validate the password for the concrete user and not the OAuth client?
Ok, I think I resolved this issue by revoking the access token via injected TokenStore in my filter (I used BearerTokenExtractor to get the token value) which seems pretty logical in this situtation. I still had no time to figure out, why my filter gets placed after OAuth2AuthenticationProcessingFilter, though.
I'm using Spring Security's ability to auto login by unauthenticated request, when it looks at request url and determines whether user is using correct token or no, then do auto login on success and show message on failure:
public class LinkRememberMeService implements RememberMeServices, LogoutHandler
{
#Autowired
private UserAccountProvider userAccountProvider;
#Override
public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response)
{
//if ok - return authentication object
//if not ok - retun null then spring redirects to default intercept-url
//but instead of retuning null i need to render view with error message for this case
}
.....
}
autoLogin can return authentication object or null, when object is returned (token is ok) user is automatically logged in, that's fine. However in case of bad token you have only one option to return null, and Spring redirects to default entry url (ex. '/login').
In case of bad token i want to either render thymeleaf template or redirect to failure url, but i can't find a way to make it work.
I've a set of APIs under /api. If my shiro.ini lists this as:
/api/** = authcBasic
Then basic auth is required. If anon is present in place of authcBasic then no auth is required. I'd like to be able to use the APIs with basic auth so I can e.g. programatically check the user is authenticated for POSTs and yet still allow anonymous access to GETs on the same URI. Alternatively to hide restricted data at the same URI for anonymous users and reveal it for auth'd users.
Is this possible?
You can roll your own custom shiro filter. Extend class BasicHttpAuthenticationFilter and override onPreHandle where you can check the servlet request method if it is GET or POST and act on it.
So something like:
public class MyFilter extends BasicHttpAuthenticationFilter {
#Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {
if ("GET".equals((HttpServletRequest)request).getMethod()){
return true;
}
return super.onPreHandle(request, response, mappedValue);
}
}
And in shiro.ini:
[main]
myfilter = mypackage.MyFilter
[urls]
/api/** = myfilter
Have you tried:
/api/** = authcBasic[permissive]
if user/password is set, shiro sends 401 if they are wrong
if user/password is not set, no 401. SecurityUtils.getSubject().authenticated is false
I think this works.
#Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
boolean loggedIn = false; //false by default or we wouldn't be in this method
if (isLoginAttempt(request, response)) {
loggedIn = executeLogin(request, response);
}
if (!loggedIn) {
// sendChallenge(request, response);
return true;
}
return loggedIn;
}
i.e. if authorisation details are provided execute login as normal (401 if auth details are invalid), else allow them in anyway (then check if authenticated, authorised later).
There's a caveat to this method though in that while it works with curl tests, using Apache's HttpClient Fluent API seems to send a request without authorisation and then send a second request with the credentials after a challenge response, which we're obviously now not sending. Arguably a bug in HttpClient but seeing as we've presumably deviated from the basic auth spec it's probably asking for it. So YMMV. This can be worked around by using preemptive auth and specifying the header value as suggested here.