I have a Customer class which has all the account info.(it does NOT extend Spring's userdetails.User class)
I'm trying to do some stuff after a successful login (e.g. set new last login time). To achieve this I set up a custom AuthenticationSuccessHandler.
In the onAuthenticationSuccess method I try to get the username from the Authentication object. This object however is a User object. If I try to get the username from it I get null.
Can I somehow manage to make the Authority object a Customer object? Or does my Customer class have to extend the User class?
Update
Some more details:
I have my User class. It is completely self written and doesn't implement or extend any interface/class. I do not have a class that implements a UserDetailsService. The <form-login> part of my applicationContext-security.xml looks like this:
<form-login login-page="/index.htm"
authentication-success-handler-ref='authSuccHandler'
authentication-failure-handler-ref='authFailureHandler'
default-target-url='/library/login.htm'
always-use-default-target='true'/>
Theh authSuccHandler looks like this: (The necessary bean definition is in place)
public class PostSuccessfulAuthenticationHandler extends SimpleUrlAuthenticationSuccessHandler
{
#Autowired
private UserService userService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException
{
userService.trackUserLogin(authentication.getName()); //NullPointerException
super.onAuthenticationSuccess(request, response, authentication);
}
}
The form redirects to j_spring_security_check
Authentication cannot be User, since they don't inherit each other.
If your UserDetailsService produces a custom UserDetails, you should be able to obtain it by calling getDetails() on Authentication.
When the request comes into the authentication success handler, it expects you to redirect to the desired URL. Use the following to redirect to the desired page like home.htm.
This will work definitely!
The modified code is given below. Check it and let me know if you have any issues.
public class PostSuccessfulAuthenticationHandler extends SimpleUrlAuthenticationSuccessHandler
{
#Autowired
private UserService userService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException
{
userService.trackUserLogin(authentication.getName()); //NullPointerException
response.sendRedirect("home.htm");
//super.onAuthenticationSuccess(request, response, authentication);
}
}
I think the method you are looking for is getPrincipal on Authentication. Then you have to case the object that comes back to your custom class.
User user = (User)authentication.getPrincipal();
Related
Providing an AuthenticationSuccessHandler for a RememberMeAuthenticationFilter breaks the filter chain, therefore I would like to override its onSuccessfulAuthentication method by providing a custom implementation of RememberMeAuthenticationFilter. But that seems to be quite complicated or elaborate when using simple Java Config.
Providing an ApplicationEventPublisher is not a solution if one needs access to HttpServletRequest or HttpServletResponse.
I managed to do it, but it looks like a hack - is there a better way?
I've done it this way:
http.rememberMe().addObjectPostProcessor(new ObjectPostProcessor<RememberMeAuthenticationFilter>() {
#Override
public <O extends RememberMeAuthenticationFilter> O postProcess(O object) {
RememberMeAuthenticationFilter newFilter = new RememberMeAuthenticationFilter(
(AuthenticationManager) getByReflection(object, "authenticationManager"),
(RememberMeServices) getByReflection(object, "rememberMeServices")
) {
#Override
protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
// business logic
}
};
return (O) newFilter;
}
private <O extends RememberMeAuthenticationFilter> Object getByReflection(O object, String name) {
Field field = ReflectionUtils.findField(object.getClass(), name);
ReflectionUtils.makeAccessible(field);
return ReflectionUtils.getField(field, object);
}
});
If you want to implement a custom behavior when authentication process (with remember me feature) is success you can try:
CustomRememberMeAuthenticationFilter
Define a new filter such as:
public class CustomRememberMeAuthenticationFilter extends RememberMeAuthenticationFilter {
#Override
protected void onSuccessfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final Authentication authResult) {
super.onSuccessfulAuthentication(request, response, authResult);
if (authResult != null) {
// process post authentication logic here..
}
}
}
Set the customer filer in security chain:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/","/login*").permitAll()
//...
http
.addFilter(rememberMeAuthenticationFilter())
//...
}
#Bean
protected RememberMeAuthenticationFilter rememberMeAuthenticationFilter(){
return new CustomRememberMeAuthenticationFilter(authenticationManager(),rememberMeServices());
}
Check this in order to create your (authenticationManager(),rememberMeServices()
In the previous snippet, custom filter is just added. If does not works, you must research and find the exact Filter in the chain to insert your custom filter: addFilterBefore, addFilterAfter, addFilterAt.
Check this add filter methods
Finally remove the default http.rememberMe() in order to use your own filter. Because the remember-me namespace element already inserts a RememberMeAuthenticationFilter so it will still take precedence over yours, since it comes before it in the filter chain.
References
https://github.com/DGYao/spring-boot-demo/blob/master/src/main/java/com/springboot/web/WebSecurityConfigurer.java
https://craftingjava.com/blog/user-management-remember-me-jwt-token/
How can I use a custom configured RememberMeAuthenticationFilter in spring security?
https://www.baeldung.com/spring-security-remember-me
https://www.baeldung.com/spring-security-custom-filter#1-java-configuration
https://stackoverflow.com/a/22668530/3957754
https://docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#remember-me-impls
How can I use a custom configured RememberMeAuthenticationFilter in spring security?
https://docs.spring.io/spring-security/site/migrate/current/3-to-4/html5/migrate-3-to-4-jc.html
persisted remember-me authentication after using custom filter
https://www.codejava.net/coding/how-to-implement-remember-password-remember-me-for-java-web-application
Spring Security custom RememberMeAuthenticationFilter not getting fired
I am working on a Spring Boot RESTful application which will be exposing a bunch of APIs for the web app to perform CRUD operations on the resources.
I am using spring-data-rest (along with spring-data-jpa of course) to expose the entities/repositories with the help of Spring Magic.
Even though I have secured (role-based) the endpoints with spring-security, it is not completely secure.
For example:
I have a User entity with has one-to-many relationship with Car. So the endpoint (auto exposed by spring-data-rest) for getting a user's cars is localhost:8080/users/{userId}/cars
However, any user with the required role can just pass the userId of another user and still access the endpoint.
The behavior I want is to secure these endpoints in a way that if I a logged-in user's ID is 1, then we can only hit localhost:8080/users/1/cars. Any other request with any other userId should end up in 403 or something.
Note: I know if write my own controllers then I can get a handle of the principal and do what I desire. I just want to know is there a way or pattern in spring-data-rest to achieve this?
Since you have already secured the application with Spring Security , here is another alternative with Method Security Expressions
Please review the #Pre and #Post Annotations for your requirement.
You may store the logged-in user's userId to the Authentication object.Details here.
Secure the required method with the #PreAuthorize annotation as follows
#PreAuthorize("#user.userId == authentication.principal.userId")
public List<Car> getCars(User user){..}
Do remember to enable method security
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {..}
To achieve that you need to write an Interceptor.It will be used under following situation:
Before sending the request to the controller
Before sending the response to the client
Before writing any Interceptor it should implement the HandlerInterceptor interface.
Three methods Interceptor supports are :
preHandle() method − Perform operations before sending the request
to the controller. This method should return true to return the
response to the client.
postHandle() method − Used to perform operations before sending
the response to the client.
afterCompletion() method − This is used to perform operations
after completing the request and response.
Code :
#Component
public class MyInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String pathVariablesMap = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
//From this pathVariablesMap extract UserId and match with a loggedinUserId
}
#Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler,
) throws Exception {}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception exception) throws Exception {}
}
By using a InterceptorRegistry you can register your Interceptors like below :
#Component
public class MyRegistoryConfig extends WebMvcConfigurer{
#Autowired
MyInterceptor myInterceptor ;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor );
}
}
For more Info follow this link Interceptors
EDIT : As #Ritesh suggested added that point.
You're using spring security(great :D), so it's better to create a simple filter, register it, then simply do your custom authorize in that filter.
In brief
Create a Custom filter
Get userId from the URL path
Get userId from SecurityContextHolder (Authenticated user principal)
Compare fetched userIds
Register filter in spring security config (After BasicAuthenticationFilter)
1- Create a custom filter (Pseudo-code)
public class CustomFilter extends GenericFilterBean {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//Fetch userId from path
HttpServletRequest req = (HttpServletRequest) request;
String path = req.getContextPath();
//..
//Fetch userId from SecurityContextHolder (User Principal)
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
Long userId = user.getId();
//Compare userId (fethced from path) with authenticated userId (fetched from principal)
//If comparison is ok
chain.doFilter(request, response);
//else
//throw Unauthorize
}
2- Register a filter in spring security config (After BasicAuthenticationFilter.class)
#Configuration
public class Configuration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(
new CustomFilter(), BasicAuthenticationFilter.class);
}
}
With this structure when an authenticated user sends a request, the request will be first checked (Comparison between userIds) and then sent.
More information for creating a filter in spring security:
A Custom Filter in the Spring Security Filter Chain
In JSP I can get username by ${pageContext.request.remoteUser}. But there is also additional info (rating of user) I need to display on every page of my site. How can I access it, considering there is a #Service to get it by username?
For what it's worth I use custom authentication provider:
#Service
public class MyUserDetailsService implements UserDetailsService {
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return new User(s, "password", Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
}
}
<security:authentication-manager>
<security:authentication-provider user-service-ref='myUserDetailsService'/>
</security:authentication-manager>
You could create implementation of AuthenticationSuccessHandler and set an attribute there:
#Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
request.getSession().setAttribute("someDetail", "detailsValue");
response.sendRedirect("/to-whatever-url-you-want")
}
}
Upon successful login, someDetail attribute will be set. Note that you can also obtain currently logged in user from Authentication instance and perform some logic.
You can create a custom UserDetails class (e.g. MyUserDetails) and save the extra information there. In your UserDetailsService, just return this MyUserDetails instead of the normal UserDetail.
public class MyUserDetails extends UserDetail {
private int rating;
... // other properties
... // getter setter
}
#Service
public class MyUserDetailsService implements UserDetailsService {
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return new MyUserDetails(...);
}
}
In every controller, you can call
(MyUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
to the get the current principal/UserDetails, which contains your extra info(e.g. rating of the user).
P.s. If this extra info is related to users, sessions are not the right place to store it, because sessions may expire after closing the browser. If this extra info is just some temporary data, then #Branislav Lazic's answer is correct. Since I can't add a comment, so I have to write the comments to #Branislav Lazic's answer here.
I'm trying to implement an action required screen after user is logged-in in Spring Security? I have a requirement where user has to perform to complete a form (change password, accept Terms Of Use, etc.), then once user completes that action he can use the rest of the app. I'm using Spring OAuth2 with the login screen that uses Spring Security flow.
So far I have tried to use http.formLogin().successHandler() that has custom implementation of SavedRequestAwareAuthenticationSuccessHandler, which detects if user has action required, then redirects user to the page when he can fill out the form, but the problem with that is that if user navigates away from that page, he will be logged in to the app and can use it without by skipping the form. But what I'm trying to do is to block user from establishing the session until after that Action Required form is complete. Once it is complete user should be automatically logged in (ex. if user was req. to only agree with Terms of Use, he should be logged in without entering a password second time)
Here is the code that I have so far the custom handler:
public class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
#Autowired
UserService userService;
public final static String TARGET_URL_SESSION_ATTR_NAME = "target-url";
public CustomLoginSuccessHandler(String defaultTargetUrl) {
setDefaultTargetUrl(defaultTargetUrl);
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
HttpSession session = request.getSession();
AuthorityUser authorityUser = (AuthorityUser)authentication.getPrincipal();
String userId = authorityUser.getUserId();
User u = userService.getById(userId);
Boolean changeRequiredDob = u.getChangeRequiredDob();
Boolean changeRequiredPwd = u.getChangeRequiredPwd();
Boolean changeRequiredTou = u.getChangeRequiredTou();
if(changeRequiredDob || changeRequiredPwd || changeRequiredTou){
String targetUrl = determineTargetUrl(request, response);
session.setAttribute(TARGET_URL_SESSION_ATTR_NAME, targetUrl);
getRedirectStrategy().sendRedirect(request, response, "/action-required");
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
And then once it is successfully complete I'm redirecting user to TARGET_URL_SESSION_ATTR_NAME that was stored to the session.
It would be also helpful to know how to detect and redirect user to the action required screen during the established sessions (if user logged in and later while he is logged in admin sets action required flag on his account).
https://github.com/king-julien/spring-oauth2-customfilter Here is a working sample with Authorization and Resource Server. This Resource Server (vanilla) is a basic stateless application which will not proceed any further until you accept Terms of Service (to accept TOS, Just a do a POST on /tos end point) after authentication.
Create a filter
#Component
public class TosFilter extends OncePerRequestFilter{
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
System.out.println(request.getRequestURI());
// In realworld scenario HelloWorldController.acceptedTOS is a persisted value rather than a static variable
if(!HelloWorldController.acceptedTOS){
//response.sendRedirect("/no-tos");
request.getRequestDispatcher("error-no-tos").forward(request, response);
}
filterChain.doFilter(request,response);
}
}
Register that filter
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
TosFilter rolesFilter;
#Override
public void configure(HttpSecurity httpSecurity) throws Exception{
httpSecurity
.addFilterAfter(rolesFilter, AbstractPreAuthenticatedProcessingFilter.class)
.csrf().disable()
.authorizeRequests().anyRequest().permitAll();
}
}
Annotate your main with #EnableResourceServer.
#SpringBootApplication
#EnableResourceServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
The way we solve that is to have the OAuth2 approval page be a single page application.
By default the approval page controller is WhitelabelApprovalEndpoint. We override this by defining out own OauthApproval Controller which overrides "/oauth/confirm_access", so we can add extra stuff to the model. When the approval (jsp) page is loaded, we convert some of the model properties into javascript variables (var token = '${_csrf.token}';), and start an AngularJS application. The approval page can then do whatever it wants (before showing the actual approval form), we just need to build REST endpoints for the different functionalities.
Remember to add #SessionAttributes("authorizationRequest") to the Controller
Instead of AuthenticationSuccessHandler you should use filter:
public class ActionRequirementCheckingFilter extends OncePerRequestFilter {
/* This matcher should not match static resources (js,css etc),
* url`s needed to handle the action and possibly something else,
* depending on your application */
private RequestMatcher matcher;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpSession session = request.getSession();
Boolean actionRequired = false;
/* calculate actual value for actionRequired */
if(matcher.matches(request) && actionRequired){
/* save current request info into session for later use if needed */
response.sendRedirect("/action-required");
} else {
filterChain.doFilter(request, response);
}
}
}
This approach fits all your requirments:
User wont be able to navigate away from it
User will be automatically logged-in after action is complete
It will even work for existing sessions
Only drawback is that session will be actualy created before action is completed, but unless you have a real reason not to do that (which i cant even imaging) this is negligible.
Another way of verifying user access rights during a successfully logged in session is via the filter api
https://www.mkyong.com/spring-mvc/how-to-register-a-servlet-filter-in-spring-mvc/
You can then implement the functionality needed in the doFilter() to verify your rules.
I have a CustomLoginSucessHandler in my Spring MVC 4 project to manage an action when the user Logs In.
This is working properly. In the same class I have the method determineTargetUrl to redirect the user according to his ROLE.
Here is the code:
#Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response){
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
final String userName = authentication.getName();
log.debug("TARGET URL METHOD!");
List<Authority> authorityList = authorityService.getAllAuthoritiesByUserName(userName);
for(Authority authority: authorityList){
switch (authority.getAuthority()){
case "ROLE_ADMIN":
return "processFile";
case "ROLE_USER":
return "userPortal";
case "ROLE_DEMO1":
return "processFile";
case "ROLE_DEMO2":
return "processFile";
}
}
return "403";
}
See that I have a log.debug("TARGET URL METHOD")
This log is never called and of course the page is not being redirected, it's going to the default landing page that is processFile.html.
I am puzzled why the second method is not being called while my onAuthenticationSuccess works perfectly. They are in the same Class.
Here is the code how I create the instance of my CustomLoginSucessHandler:
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private CustomLoginSucessHandler customLoginSucessHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin().loginPage("/login.html")
.loginProcessingUrl("/login").permitAll().and().logout().logoutSuccessUrl("/")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).permitAll().and().exceptionHandling()
.accessDeniedPage("/403.html");
http.csrf().requireCsrfProtectionMatcher(new CsrfRequestMatcher());
http.formLogin().successHandler(customLoginSucessHandler);
}
Thank you.
You are trying to ovverride the wrong function, that is the root cause of your issue. In the excerpt you provided you have a function that seems to be overriding another:
#Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response){
but in fact it is not overriding anything. If you check the javadoc of AuthenticationSuccessHandler, you will see that it provides only one function: onAuthenticationSuccess which you reported as "working". It works, but it is an overriden function and it does get called as part of the standard login procedure. If you follow closely this example:
CustomLoginSuccessHandler example (probably you followed this already)
you will see that the determineTargetUrl function is not overriden, but explicitly called by the implementation:
protected void handle(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException {
String targetUrl = determineTargetUrl(authentication);
which handle method in turn is also being called from:
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException {
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}