How can I enable request only for user's own endpoint - java

I have a rest-endpoint like this:
/users/{userId}/something
I implemented authentification using oauth2.
My WebSecurityConfig looks like this:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll();
}
How can I only allow users to access their own endpoint (eg. User with Id 100 can only access /users/100/something) without beeing able to see another endpoint (like /users/200/something)?
Is this possible?

There are many ways to solve this problem, but i have picked out three solutions to apraoch this problem.
Custom Security Expression
I would recommend a custom security based annotation approach. This would involve implementing a custom security expression, the related expression handler and the method security configuration. The next appraoch is a little bit simpler if this is too much work for you.
public class UserIdentityMethodSecurityExpressionRoot
extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
public UserIdentityMethodSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
public boolean userIdentity(Long userId) {
User user = ((UserPrincipal) this.getPrincipal()).getUser();
return user.getId() == userId;
}
}
Rest endpoints or service methods can then be annotated with the newly created security expression:
#PreAuthorize("userIdentity(#userId)")
#GetMapping
#ResponseBody
public Resource fineOne(#PathVariable Long userId) {
return resourceService.findOne(id);
}
Please note that the userId must be provided somewhere, either as #PathVariable or #RequestParam. Spring security will then check if the current user has matches the provided userId and returns 403 otherwise.
The full example is available here and has been adapted in this question for your purposes.
SpEL
You can also use SpEL, which is a little bit simpler:
#PreAuthorize("#userId == principal.getId()")
#GetMapping
#ResponseBody
public Resource fineOne(#PathVariable Long userId) {
return resourceService.findOne(id);
}
Other considerations
You can also do all the work yourself and get to a faster result without defining a custom expression using SecurityContextHolder.
public static void checkUserIdentity(Long userId) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// user did not provide a token
if(auth == null) {
throw new AccessDeniedException();
}
UserDetails details = (UserDetails) auth.getPrincipal();
if(userId != details.getId()) {
throw new AccessDeniedException();
}
}
And using it like:
#GetMapping
#ResponseBody
public Resource fineOne(#PathVariable Long userId) {
SecurityUtils.checkUserIdentity(userId)
return resourceService.findOne(id);
}
Why does this work? The SecurityContextHolder will inject the current principal if you have setup Spring security correctly. By default, an authentication is bound to the current thread of execution and will be reset if the request has been processed or encounters an exception.

Related

Springboot role authentication not working. I cannot access a specific path, [duplicate]

I’m unable to use hasRole method in #PreAuthorize annotation. Also request.isUserInRole(“ADMIN”) gives false. What am I missing?
Although .hasAuthority(“ADMIN”) works fine.
I am assigning authorities to the users from a database.
You have to name your authority with prefix ROLE_ to use isUserInRole, see Spring Security Reference:
The HttpServletRequest.isUserInRole(String) will determine if SecurityContextHolder.getContext().getAuthentication().getAuthorities() contains a GrantedAuthority with the role passed into isUserInRole(String). Typically users should not pass in the "ROLE_" prefix into this method since it is added automatically. For example, if you want to determine if the current user has the authority "ROLE_ADMIN", you could use the following:
boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
Same for hasRole (also hasAnyRole), see Spring Security Reference:
Returns true if the current principal has the specified role. By default if the supplied role does not start with 'ROLE_' it will be added. This can be customized by modifying the defaultRolePrefix on DefaultWebSecurityExpressionHandler.
See also Spring Security Reference:
46.3.3 What does "ROLE_" mean and why do I need it on my role names?
Spring Security has a voter-based architecture which means that an access decision is made by a series of AccessDecisionVoters. The voters act on the "configuration attributes" which are specified for a secured resource (such as a method invocation). With this approach, not all attributes may be relevant to all voters and a voter needs to know when it should ignore an attribute (abstain) and when it should vote to grant or deny access based on the attribute value. The most common voter is the RoleVoter which by default votes whenever it finds an attribute with the "ROLE_" prefix. It makes a simple comparison of the attribute (such as "ROLE_USER") with the names of the authorities which the current user has been assigned. If it finds a match (they have an authority called "ROLE_USER"), it votes to grant access, otherwise it votes to deny access.
I had to improvise a little, maybe there is other ways simpler then mine, but at the time I worked on this I had no other choice but to improvise a bit, after a thorough research came up with this solution.
Spring Security has an interface called AccessDecisionManager, you will need to implement it.
#Component
public class RolesAccessDecisionManager implements AccessDecisionManager {
private final static String AUTHENTICATED = "authenticated";
private final static String PERMIT_ALL = "permitAll";
#Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
collection.forEach(configAttribute -> {
if (!this.supports(configAttribute))
throw new AccessDeniedException("ACCESS DENIED");
});
}
#Override
public boolean supports(ConfigAttribute configAttribute) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
String rolesAsString = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(","));
if (configAttribute.toString().contains(rolesAsString))
return true;
else
return (configAttribute.toString().contains(PERMIT_ALL) || configAttribute.toString().contains(AUTHENTICATED));
}
return true;
}
#Override
public boolean supports(Class<?> aClass) {
return true;
}
}
Now to support this custom access-decision-manager with your security config do this in the security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
// other configs
.accessDecisionManager(this.accessDecisionManager)
accessDecisionManager is the autowired bean of the AccessDecisionManager implementation you've created.
You can use either hasRole() or hasAuthority(). The difference is that, you have to user ROLE_ for hasAusthority() method.
So for the ROLE_ADMIN,
#PreAuthorize("hasRole('ADMIN')") == #PreAuthorize("hasAuthority('ROLE_ADMIN')")

How to get current user in every request in Spring Boot?

I would like to get the username of the user in every request to add them to log file.
This is my solution:
First, I created a LoggedUser with a static property:
public class LoggedUser {
private static final ThreadLocal<String> userHolder =
new ThreadLocal<>();
public static void logIn(String user) {
userHolder.set(user);
}
public static void logOut() {
userHolder.remove();
}
public static String get() {
return userHolder.get();
}
}
Then I created a support class to get username:
public interface AuthenticationFacade {
Authentication getAuthentication();
}
#Component
public class AuthenticationFacadeImpl implements AuthenticationFacade {
#Override
public Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
Finally, I used them in my Controllers:
#RestController
public class ResourceController {
Logger logger = LoggerFactory.getLogger(ResourceController.class);
#Autowired
private GenericService userService;
#Autowired
private AuthenticationFacade authenticationFacade;
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
loggedUser.logIn(authenticationFacade.getAuthentication().getName());
logger.info(LoggedUser.get()); //Log username
return userService.findAllRandomCities();
}
}
The problem is I don't want to have AuthenticationFacade in every #Controller, If I have 10000 controllers, for example, it will be a lot of works.
Do you have any better solution for it?
The solution is called Fish Tagging. Every decent logging framework has this functionality. Some frameworks call it MDC(Mapped Diagnostic Context). You can read about it here and here.
The basic idea is to use ThreadLocal or InheritableThreadLocal to hold a few key-value pairs in a thread to track a request. Using logging configuration, you can configure how to print it in the log entries.
Basically, you can write a filter, where you would retrieve the username from the security context and put it into the MDC and just forget about it. In your controller you log only the business logic related stuff. The username will be printed in the log entries along with timestamp, log level etc. (as per your log configuration).
With Jhovanni's suggestion, I created an AOP annotation like this:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface LogUsername {
}
In the same package, I added new #Aop #Component class with AuthenticationFacade injection:
#Aspect
#Component
public class LogUsernameAop {
Logger logger = LoggerFactory.getLogger(LogUsernameAop.class);
#Autowired
private AuthenticationFacade authenticationFacade;
#Before("#annotation(LogUsername)")
public void logUsername() throws Throwable {
logger.info(authenticationFacade.getAuthentication().getName());
LoggedUser.logIn(authenticationFacade.getAuthentication().getName());
}
}
Then, in every #GetMapping method, If I need to log the username, I can add an annotation before the method:
#PostMapping
#LogUsername
public Course createCourse(#RequestBody Course course){
return courseService.saveCourse(course);
}
Finally, this is the result:
2018-10-21 08:29:07.206 INFO 8708 --- [nio-8080-exec-2] com.khoa.aop.LogUsername : john.doe
Well, you are already accesing authentication object directly from SecurityContextHolder, you can do it in your controller.
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(authentication != null){
//log user name
logger.info(authentication.get());
}
return userService.findAllRandomCities();
}
If you do not want to put all this in every endpoint, an utility method can be created to extract authentication and return its name if found.
public class UserUtil {
public static String userName(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication == null ? null : authentication.getName();
}
}
and call it in your endpoint like
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
//log user name
logger.info(UserUtil.username());
return userService.findAllRandomCities();
}
However, you are still adding lines of code in every endpoint, and after a few of them it starts to feel wrong being forced to do it. Something I suggest you to do is try aspect oriented programming for this kind of stuff. It will require you to invest some time in learning how it works, create annotations or executions required. But you should have it in a day or two.
With aspect oriented your endpoint could end like this
#RequestMapping(value ="/cities")
#LogUserName
public List<RandomCity> getCitiesAndLogWhoIsRequesting(){
//LogUserName annotation will inform this request should log user name if found
return userService.findAllRandomCities();
}
of course, you are able to remove #LogUserName custom annotation and configure the new aspect with being triggered by methods inside a package, or classes extending #Controller, etc.
Definitely it is worth the time, because you can use aspect for more than just logging user name.
You can obtain the username via request or parameter in your controller method. If you add Principal principal as a parameter, Spring Ioc Container will inject the information regarding the user or it will be null for anonymous users.
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(Principal principal){
if(principal == null){
// anonymous user
}
}
There are various ways in Spring Security to fetch the user details from the security context. But according to your requirement, you are only interested in username, so you can try this:
#RequestMapping(value ="/cities")
public List<RandomCity> getCitiesAndLogWhoIsRequesting(Authentication authentication){
logger.info(authentication.getName()); //Log username
return userService.findAllRandomCities();
}
Hope this helps!

Spring Security - HTTP Status 403 – Forbidden - The server understood the request but refuses to authorize it [duplicate]

I’m unable to use hasRole method in #PreAuthorize annotation. Also request.isUserInRole(“ADMIN”) gives false. What am I missing?
Although .hasAuthority(“ADMIN”) works fine.
I am assigning authorities to the users from a database.
You have to name your authority with prefix ROLE_ to use isUserInRole, see Spring Security Reference:
The HttpServletRequest.isUserInRole(String) will determine if SecurityContextHolder.getContext().getAuthentication().getAuthorities() contains a GrantedAuthority with the role passed into isUserInRole(String). Typically users should not pass in the "ROLE_" prefix into this method since it is added automatically. For example, if you want to determine if the current user has the authority "ROLE_ADMIN", you could use the following:
boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
Same for hasRole (also hasAnyRole), see Spring Security Reference:
Returns true if the current principal has the specified role. By default if the supplied role does not start with 'ROLE_' it will be added. This can be customized by modifying the defaultRolePrefix on DefaultWebSecurityExpressionHandler.
See also Spring Security Reference:
46.3.3 What does "ROLE_" mean and why do I need it on my role names?
Spring Security has a voter-based architecture which means that an access decision is made by a series of AccessDecisionVoters. The voters act on the "configuration attributes" which are specified for a secured resource (such as a method invocation). With this approach, not all attributes may be relevant to all voters and a voter needs to know when it should ignore an attribute (abstain) and when it should vote to grant or deny access based on the attribute value. The most common voter is the RoleVoter which by default votes whenever it finds an attribute with the "ROLE_" prefix. It makes a simple comparison of the attribute (such as "ROLE_USER") with the names of the authorities which the current user has been assigned. If it finds a match (they have an authority called "ROLE_USER"), it votes to grant access, otherwise it votes to deny access.
I had to improvise a little, maybe there is other ways simpler then mine, but at the time I worked on this I had no other choice but to improvise a bit, after a thorough research came up with this solution.
Spring Security has an interface called AccessDecisionManager, you will need to implement it.
#Component
public class RolesAccessDecisionManager implements AccessDecisionManager {
private final static String AUTHENTICATED = "authenticated";
private final static String PERMIT_ALL = "permitAll";
#Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
collection.forEach(configAttribute -> {
if (!this.supports(configAttribute))
throw new AccessDeniedException("ACCESS DENIED");
});
}
#Override
public boolean supports(ConfigAttribute configAttribute) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
String rolesAsString = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(","));
if (configAttribute.toString().contains(rolesAsString))
return true;
else
return (configAttribute.toString().contains(PERMIT_ALL) || configAttribute.toString().contains(AUTHENTICATED));
}
return true;
}
#Override
public boolean supports(Class<?> aClass) {
return true;
}
}
Now to support this custom access-decision-manager with your security config do this in the security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
// other configs
.accessDecisionManager(this.accessDecisionManager)
accessDecisionManager is the autowired bean of the AccessDecisionManager implementation you've created.
You can use either hasRole() or hasAuthority(). The difference is that, you have to user ROLE_ for hasAusthority() method.
So for the ROLE_ADMIN,
#PreAuthorize("hasRole('ADMIN')") == #PreAuthorize("hasAuthority('ROLE_ADMIN')")

How do I decide which Persistent Token to delete in Spring Security PersistentTokenRepository.removeUserTokens

I've implemented Persistent token based Remember Me with Spring Security 3.2.3.RELEASE.
During development and testing I realized the database is filled with tokens for the same username.
When removeUserTokens is called I don't know which of the tokens I need to delete. I guess that the user have multiple tokens, one for each device he's using (Computer, Android, etc...), and if he logs out of one device, I want to delete the token for that device so he stays logged in another device.
Any ideas?
1) create custom PersistentTokenRepository with one additional method:
public class MDJdbcTokenRepository extends JdbcTokenRepositoryImpl {
public void removeTokenBySeries(String series) {
getJdbcTemplate().update("delete from persistent_logins where series = ?", series);
}
}
2) create custom RememeberMeServices
public class MDRememberMeServices extends PersistentTokenBasedRememberMeServices {
private MDJdbcTokenRepository tokenRepository;
public MDRememberMeServices(String key,
UserDetailsService userDetailsService,
MDJdbcTokenRepository tokenRepository) {
super(key, userDetailsService, tokenRepository);
setParameter("remember-me");// parameter name in login form
this.tokenRepository = tokenRepository;
}
public void logout(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
cancelCookie(request, response);
if (authentication != null) {
String rememberMeCookie = extractRememberMeCookie(request);
if(rememberMeCookie != null && rememberMeCookie.length() != 0) {
String[] cookieTokens = decodeCookie(rememberMeCookie);
if (cookieTokens.length == 2) {
String series = cookieTokens[0];
//remove by series
tokenRepository.removeTokenBySeries(series);
}
}
}
}
}
3) add beans
private static String key = "your string here";
#Bean
public MDJdbcTokenRepository persistentTokenRepository() {
MDJdbcTokenRepository db = new MDJdbcTokenRepository();
db.setDataSource(dataSource());
return db;
}
#Bean
public RememberMeServices rememberMeServices() throws Exception {
return new MDRememberMeServices(key, userDetailsService, persistentTokenRepository());
}
4) change your security spring config:
and().rememberMe().key(key).rememberMeServices(rememberMeServices())
The easiest solution is to change the Authentication object you use in such a way that calls to the getName() return "deviceID"+"username" instead of "username".
You could for example hash HTTP header UserAgent and use it as a device ID. The call to PersistentTokenRepository.removeUserTokens will then always only remove tokens related to the particular device.
The value returned from the getName() call can be customized by changing logic in your UserDetailsService implementation, as the Authentication.getName() typically delegates to UserDetails.getUsername() of the object returned from the UserDetailsService.
Other possible solutions will most likely require custom implementation of the org.springframework.security.web.authentication.RememberMeServices interface.
I am also a newbie in this world of Spring but I hope I can give you a tip...
Taking a look at PersistentTokenRepository you can notice that the only way to get the remember-me token is by using the seriesId stored in the cookie of your local browser. That way, when you log out on that device you only remove the "token) (actually the entire entry) of the corresponding seriesId.
So my try is to recommend you to just log out your user in order to delete its associated token.
I really hope that helps!

When using Spring Security, what is the proper way to obtain current username (i.e. SecurityContext) information in a bean?

I have a Spring MVC web app which uses Spring Security. I want to know the username of the currently logged in user. I'm using the code snippet given below . Is this the accepted way?
I don't like having a call to a static method inside this controller - that defeats the whole purpose of Spring, IMHO. Is there a way to configure the app to have the current SecurityContext, or current Authentication, injected instead?
#RequestMapping(method = RequestMethod.GET)
public ModelAndView showResults(final HttpServletRequest request...) {
final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
...
}
If you are using Spring 3, the easiest way is:
#RequestMapping(method = RequestMethod.GET)
public ModelAndView showResults(final HttpServletRequest request, Principal principal) {
final String currentUser = principal.getName();
}
A lot has changed in the Spring world since this question was answered. Spring has simplified getting the current user in a controller. For other beans, Spring has adopted the suggestions of the author and simplified the injection of 'SecurityContextHolder'. More details are in the comments.
This is the solution I've ended up going with. Instead of using SecurityContextHolder in my controller, I want to inject something which uses SecurityContextHolder under the hood but abstracts away that singleton-like class from my code. I've found no way to do this other than rolling my own interface, like so:
public interface SecurityContextFacade {
SecurityContext getContext();
void setContext(SecurityContext securityContext);
}
Now, my controller (or whatever POJO) would look like this:
public class FooController {
private final SecurityContextFacade securityContextFacade;
public FooController(SecurityContextFacade securityContextFacade) {
this.securityContextFacade = securityContextFacade;
}
public void doSomething(){
SecurityContext context = securityContextFacade.getContext();
// do something w/ context
}
}
And, because of the interface being a point of decoupling, unit testing is straightforward. In this example I use Mockito:
public class FooControllerTest {
private FooController controller;
private SecurityContextFacade mockSecurityContextFacade;
private SecurityContext mockSecurityContext;
#Before
public void setUp() throws Exception {
mockSecurityContextFacade = mock(SecurityContextFacade.class);
mockSecurityContext = mock(SecurityContext.class);
stub(mockSecurityContextFacade.getContext()).toReturn(mockSecurityContext);
controller = new FooController(mockSecurityContextFacade);
}
#Test
public void testDoSomething() {
controller.doSomething();
verify(mockSecurityContextFacade).getContext();
}
}
The default implementation of the interface looks like this:
public class SecurityContextHolderFacade implements SecurityContextFacade {
public SecurityContext getContext() {
return SecurityContextHolder.getContext();
}
public void setContext(SecurityContext securityContext) {
SecurityContextHolder.setContext(securityContext);
}
}
And, finally, the production Spring config looks like this:
<bean id="myController" class="com.foo.FooController">
...
<constructor-arg index="1">
<bean class="com.foo.SecurityContextHolderFacade">
</constructor-arg>
</bean>
It seems more than a little silly that Spring, a dependency injection container of all things, has not supplied a way to inject something similar. I understand SecurityContextHolder was inherited from acegi, but still. The thing is, they're so close - if only SecurityContextHolder had a getter to get the underlying SecurityContextHolderStrategy instance (which is an interface), you could inject that. In fact, I even opened a Jira issue to that effect.
One last thing - I've just substantially changed the answer I had here before. Check the history if you're curious but, as a coworker pointed out to me, my previous answer would not work in a multi-threaded environment. The underlying SecurityContextHolderStrategy used by SecurityContextHolder is, by default, an instance of ThreadLocalSecurityContextHolderStrategy, which stores SecurityContexts in a ThreadLocal. Therefore, it is not necessarily a good idea to inject the SecurityContext directly into a bean at initialization time - it may need to be retrieved from the ThreadLocal each time, in a multi-threaded environment, so the correct one is retrieved.
I agree that having to query the SecurityContext for the current user stinks, it seems a very un-Spring way to handle this problem.
I wrote a static "helper" class to deal with this problem; it's dirty in that it's a global and static method, but I figured this way if we change anything related to Security, at least I only have to change the details in one place:
/**
* Returns the domain User object for the currently logged in user, or null
* if no User is logged in.
*
* #return User object for the currently logged in user, or null if no User
* is logged in.
*/
public static User getCurrentUser() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal()
if (principal instanceof MyUserDetails) return ((MyUserDetails) principal).getUser();
// principal object is either null or represents anonymous user -
// neither of which our domain User object can represent - so return null
return null;
}
/**
* Utility method to determine if the current user is logged in /
* authenticated.
* <p>
* Equivalent of calling:
* <p>
* <code>getCurrentUser() != null</code>
*
* #return if user is logged in
*/
public static boolean isLoggedIn() {
return getCurrentUser() != null;
}
To make it just show up in your JSP pages, you can use the Spring Security Tag Lib:
http://static.springsource.org/spring-security/site/docs/3.0.x/reference/taglibs.html
To use any of the tags, you must have the security taglib declared in your JSP:
<%# taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
Then in a jsp page do something like this:
<security:authorize access="isAuthenticated()">
logged in as <security:authentication property="principal.username" />
</security:authorize>
<security:authorize access="! isAuthenticated()">
not logged in
</security:authorize>
NOTE: As mentioned in the comments by #SBerg413, you'll need to add
use-expressions="true"
to the "http" tag in the security.xml config for this to work.
If you are using Spring Security ver >= 3.2, you can use the #AuthenticationPrincipal annotation:
#RequestMapping(method = RequestMethod.GET)
public ModelAndView showResults(#AuthenticationPrincipal CustomUser currentUser, HttpServletRequest request) {
String currentUsername = currentUser.getUsername();
// ...
}
Here, CustomUser is a custom object that implements UserDetails that is returned by a custom UserDetailsService.
More information can be found in the #AuthenticationPrincipal chapter of the Spring Security reference docs.
I get authenticated user by
HttpServletRequest.getUserPrincipal();
Example:
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.support.RequestContext;
import foo.Form;
#Controller
#RequestMapping(value="/welcome")
public class IndexController {
#RequestMapping(method=RequestMethod.GET)
public String getCreateForm(Model model, HttpServletRequest request) {
if(request.getUserPrincipal() != null) {
String loginName = request.getUserPrincipal().getName();
System.out.println("loginName : " + loginName );
}
model.addAttribute("form", new Form());
return "welcome";
}
}
In Spring 3+ you have have following options.
Option 1 :
#RequestMapping(method = RequestMethod.GET)
public String currentUserNameByPrincipal(Principal principal) {
return principal.getName();
}
Option 2 :
#RequestMapping(method = RequestMethod.GET)
public String currentUserNameByAuthentication(Authentication authentication) {
return authentication.getName();
}
Option 3:
#RequestMapping(method = RequestMethod.GET)
public String currentUserByHTTPRequest(HttpServletRequest request) {
return request.getUserPrincipal().getName();
}
Option 4 : Fancy one : Check this out for more details
public ModelAndView someRequestHandler(#ActiveUser User activeUser) {
...
}
I would just do this:
request.getRemoteUser();
Yes, statics are generally bad - generally, but in this case, the static is the most secure code you can write. Since the security context associates a Principal with the currently running thread, the most secure code would access the static from the thread as directly as possible. Hiding the access behind a wrapper class that is injected provides an attacker with more points to attack. They wouldn't need access to the code (which they would have a hard time changing if the jar was signed), they just need a way to override the configuration, which can be done at runtime or slipping some XML onto the classpath. Even using annotation injection in the signed code would be overridable with external XML. Such XML could inject the running system with a rogue principal. This is probably why Spring is doing something so un-Spring-like in this case.
For the last Spring MVC app I wrote, I didn't inject the SecurityContext holder, but I did have a base controller that I had two utility methods related to this ... isAuthenticated() & getUsername(). Internally they do the static method call you described.
At least then it's only in once place if you need to later refactor.
You could use Spring AOP aproach.
For example if you have some service, that needs to know current principal. You could introduce custom annotation i.e. #Principal , which indicate that this Service should be principal dependent.
public class SomeService {
private String principal;
#Principal
public setPrincipal(String principal){
this.principal=principal;
}
}
Then in your advice, which I think needs to extend MethodBeforeAdvice, check that particular service has #Principal annotation and inject Principal name, or set it to 'ANONYMOUS' instead.
The only problem is that even after authenticating with Spring Security, the user/principal bean doesn't exist in the container, so dependency-injecting it will be difficult. Before we used Spring Security we would create a session-scoped bean that had the current Principal, inject that into an "AuthService" and then inject that Service into most of the other services in the Application. So those Services would simply call authService.getCurrentUser() to get the object. If you have a place in your code where you get a reference to the same Principal in the session, you can simply set it as a property on your session-scoped bean.
The best solution if you are using Spring 3 and need the authenticated principal in your controller is to do something like this:
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
#Controller
public class KnoteController {
#RequestMapping(method = RequestMethod.GET)
public java.lang.String list(Model uiModel, UsernamePasswordAuthenticationToken authToken) {
if (authToken instanceof UsernamePasswordAuthenticationToken) {
user = (User) authToken.getPrincipal();
}
...
}
Try this
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
String userName = authentication.getName();
I am using the #AuthenticationPrincipal annotation in #Controller classes as well as in #ControllerAdvicer annotated ones. Ex.:
#ControllerAdvice
public class ControllerAdvicer
{
private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAdvicer.class);
#ModelAttribute("userActive")
public UserActive currentUser(#AuthenticationPrincipal UserActive currentUser)
{
return currentUser;
}
}
Where UserActive is the class i use for logged users services, and extends from org.springframework.security.core.userdetails.User. Something like:
public class UserActive extends org.springframework.security.core.userdetails.User
{
private final User user;
public UserActive(User user)
{
super(user.getUsername(), user.getPasswordHash(), user.getGrantedAuthorities());
this.user = user;
}
//More functions
}
Really easy.
Define Principal as a dependency in your controller method and spring will inject the current authenticated user in your method at invocation.
I like to share my way of supporting user details on freemarker page.
Everything is very simple and working perfectly!
You just have to place Authentication rerequest on default-target-url (page after form-login)
This is my Controler method for that page:
#RequestMapping(value = "/monitoring", method = RequestMethod.GET)
public ModelAndView getMonitoringPage(Model model, final HttpServletRequest request) {
showRequestLog("monitoring");
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userName = authentication.getName();
//create a new session
HttpSession session = request.getSession(true);
session.setAttribute("username", userName);
return new ModelAndView(catalogPath + "monitoring");
}
And this is my ftl code:
<#security.authorize ifAnyGranted="ROLE_ADMIN, ROLE_USER">
<p style="padding-right: 20px;">Logged in as ${username!"Anonymous" }</p>
</#security.authorize>
And that's it, username will appear on every page after authorisation.

Categories

Resources