In this Spring Boot application there is a web service, which returns some data for a logged-in user:
#RequestMapping("/resource")
public Map<String, Object> home() {
Map<String, Object> model = new HashMap<String, Object>();
model.put("id", UUID.randomUUID().toString());
model.put("content", "Hello World");
return model;
}
Imagine, the return value of the method depends on what user is currently logged in.
How can I find out, which user is logged in in that method?
As per request:
Spring Boot which uses Spring Security internally provides a SecurityContextHolder class which allows the lookup of the currently authenticated user via:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
The authentication instance now provides the following methods:
Get the username of the logged in user: getPrincipal()
Get the password of the authenticated user: getCredentials()
Get the assigned roles of the authenticated user: getAuthorities()
Get further details of the authenticated user: getDetails()
Since Spring Security 3.2 you can get currently logged in user (your implementation of UserDetails) by adding a parameter inside your controller method:
import org.springframework.security.web.bind.annotation.AuthenticationPrincipal;
#RequestMapping("/resource")
public Map<String, Object> home(#AuthenticationPrincipal User user) {
..
}
Replace User with the name of your class which implements UserDetails interface.
Edit:
Since Spring Security 4.0 annotation was moved to a different package:
import org.springframework.security.core.annotation.AuthenticationPrincipal;
Addendum:
This will work even in WebFlux reactive environment versus the SecurityContextHolder.getContext().getAuthentication() which won't work because of paradigm shift from thread per request model to multiple requests per thread.
You can simply use HttpServletRequest also to get user principle,
using HttpServletRequest request,
String user=request.getUserPrincipal().getName();
One way is to add java.security.Principal as a parameter as follows:
#RequestMapping("/resource")
public Map<String, Object> home(Principal principal) {
Map<String, Object> model = new HashMap<String, Object>();
model.put("id", UUID.randomUUID().toString());
model.put("content", "Hello " + principal.getName());
return model;
}
Since version 5.2 you can use CurrentSecurityContext annotation:
#GetMapping("/hello")
public String hello(#CurrentSecurityContext(expression="authentication?.name")
String username) {
return "Hello, " + username + "!";
}
In Spring boot v2.1.9.RELEASE if you are trying to get the name, email, given_name you can get those details from Pricipal.
Note: I am using spring security with google oauth2.
Map<String , Object> userDetails = ((DefaultOidcUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getAttributes();
System.out.println(userDetails.get("name"));
System.out.println(userDetails.get("email"));
System.out.println(userDetails.get("given_name"));
Recently using Keycloak authentication server and accessing currently logged-in user data is accessible like this
String userID;
KeycloakPrincipal kcPrincipal = getPrincipal();
KeycloakSecurityContext ksContext = kcPrincipal.getKeycloakSecurityContext();
IDToken idToken = ksContext.getToken();
userID = idToken.getName();
Im using spring boot 2.0 with OAuth so I'm doing it like this
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Object pricipal = auth.getPrincipal();
String user="";
if (pricipal instanceof DefaultOidcUser) {
user = ((DefaultOidcUser) pricipal).getName();
}
You can find the currently logged in user name without using any spring Security features.
All you need is a jdk 1.8
Do the following :
#RequestMapping("/login")
#Override
public ModelAndView AuthChecker(#RequestParam("email") String email, #RequestParam("password") String password, Customers cust) {
ModelAndView mv = new ModelAndView("index");
if((repo.findByEmail(email)!=null) && (repo.findByPassword(password)!=null)) {
List<Customers> l= repo.findAll();
cust = (Customers) l.stream()
.filter(x -> email.equals(x.getEmail()))
.findAny()
.orElse(null);
mv.addObject("user",cust.getName());
mv.setViewName("DashBoardRedirect");
return mv;
Once name fetched successfully, you can use the same in any jsp/thymeleaf view.
Related
I've been following this tutorial in order to create an Authentication Server, but I'm facing some problems regarding the concepts, I guess.
Look, when I register a Client in Repository, I have to define some parameters, like its id, secret, authentication method, grant types, redirection uris and scopes:
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("articles-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("articles.read")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
When I'm back to my Resource Server, I find that my client was successfully logged in and it returns with an "articles.read" scope. Everything is fine here, supposing that I want to protect my endpoints with the Client's scope, but this is not my case.
In my situation, I want to protect my endpoints according to my User's role in database.
I'll give you an example, so you don't have to read the whole Baeldung's website:
I try to access: http://localhost:8080/articles.
It redirects to: http://auth-server:9000, where a Spring Security Login Form appears.
When you submit the proper credentials (which are compared from a database using the default Spring Security schema), it basically gets you back to: http://localhost:8080/articles.
Well, in that point, I have an Authorization Token with the Client scope, but not the logged User role.
Is there an standard way to configure my project to achieve this or, do I have to think of a creative way to do so?
Thank you in advance.
For role based authentication you should map authorities in Oauth token.
OAuth2AuthenticationToken.getAuthorities() is used for authorizing requests, such as in hasRole('USER') or hasRole('ADMIN').
For this you need to implement the userAuthoritiesMapper, something like this:
#Configuration
public class AppConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login().userInfoEndpoint().userAuthoritiesMapper(this.userAuthoritiesMapper());
//.oidcUserService(this.oidcUserService());
super.configure(http);
}
private GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (OidcUserAuthority.class.isInstance(authority)) {
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
if (userInfo.containsClaim("role")){
String roleName = "ROLE_" + userInfo.getClaimAsString("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
} else if (OAuth2UserAuthority.class.isInstance(authority)) {
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
if (userAttributes.containsKey("role")){
String roleName = "ROLE_" + (String)userAttributes.get("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
}
});
return mappedAuthorities;
};
}
}
Good Day.
I have a requirement to implement OAuth2 with spring-security using springboot2 basing on below URL.
URL Name:
https://www.devglan.com/spring-security/spring-boot-oauth2-jwt-example
And I did it.
Here I got another different requirement for the above code in authenticating user.
I need to pass companyId along with userName and password and grant_type(password)in x-www-form-urlencoded tab in postman tool.
And i have to fetch User based on username and companyId.
So please help me out what changes I need to do in above link code so that i can achieve my requirement.
Here i am getting only email. I need email along with companyId.
#Override
#Transactional
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email).orElseThrow(
() -> new UsernameNotFoundException("User Not Found with -> username or email : " + email));
return UserPrinciple.build(user);
}
Expected:
#Override
#Transactional
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmailAndCompanyId(email,companyId).orElseThrow(
() -> new UsernameNotFoundException("User Not Found with -> username or email : " + email));
return UserPrinciple.build(user);
}
At present with username and password it is working fine.
But in my system i have same user mapped to different companies.if same user mapped to multiple companies found i am getting error like below.
{
"error": "unauthorized",
"error_description": "query did not return a unique result: 2; nested exception is javax.persistence.NonUniqueResultException: query did not return a unique result: 2"
}
I need to fetch user, based on username and password and companyId which results in single user.
Can some one help on this issue. Thanks a lot in advance.
Bhanuprakash, i have a similar problem but temporary i did this:
In the "loadUserByUsername" method write this:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
System.out.println("compnayId: " + request.getParameter("companyId"));
I think there is a more elegant solution, but temporary i used this.
Thanks.
If you want additional information's with access tokens you can use TokenEnhancer class to do that.
CustomTokenEnhancer.java
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("id", user.getCompanyId());
additionalInfo.put("authorities", user.getAuthorities());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
Then user the instance of this class to void configure(AuthorizationServerEndpointsConfigurer endpoints) method like this
AuthorizationServerConfig.java
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
.tokenEnhancer(new CustomTokenEnhancer());
}
This comment worked for me perfectly!
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
System.out.println("compnayId: " + request.getParameter("companyId"));
I have my custom auth server implemented using spring-security-oauth along with several client applications for Single Sign On, I have the option of facebook and google login.
Now on my client application when I have this controller:
#RequestMapping({ "/user", "/me" })
public Principal user(Principal principal) {
return principal;
}
For google It works fine and returns name but for facebook it returns a unique ID. I've tried many ways but i am not able to get name from this principal object. can someone please help?
This is my short cutted facebook config:
accessTokenUri: https://graph.facebook.com/oauth/access_token
userAuthorizationUri: https://www.facebook.com/dialog/oauth
tokenName: oauth_token
authenticationScheme: query
clientAuthenticationScheme: form
userInfoUri: https://graph.facebook.com/me?fields=id,name,email
Ok i got the work around for it:
#RequestMapping(value = { "/user", "/me"}, method = RequestMethod.GET)
public Map<String, String> user(Principal principal) {
Map<String, Object> details = (Map<String, Object>) ((OAuth2Authentication) principal).getUserAuthentication().getDetails();
Map<String, String> map = new LinkedHashMap<>();
map.put("name", (String) details.get("name"));
map.put("email", (String) details.get("email"));
return map;
}
Use f_name instead of name for Facebook Graph API
I am developing an application using Spring. I have a trouble about login and logout. I logged in application using a login credentials(e.g. userName:john, pass: doe) and go to Admin page and than I logged out from application. But this time I used different login credentials(e.g. userName: jack, pass: white) for login. When I go to Admin page and debug my application #ModelAttribute(value = "myUser") User loggedInUser at AdminController shows old user value. I couldn't understand why this occurs. Anyone can help?
My source codes are below:
#Controller
#RequestMapping("/LoginController")
#SessionAttributes({"myUser"})
public class LoginController
{
private static String LOGIN_URL = "login/login_";
private static String INDEX_URL = "main/index";
#Autowired
private IUserService userService = null;
#RequestMapping("/login")
public ModelAndView login(#RequestParam(value="userName", required=false) String argUserName, #RequestParam(value="password", required=false) String argPassword, HttpServletRequest req)
{
ModelAndView modelAndView = new ModelAndView();
// Assume argUserName and argPassword not null
User loginUser = this.userService.getUser(argUserName, argPassword);
HttpSession ses = req.getSession();
// Assume loginUser not null
ses.setAttribute("myUser", loginUser);
modelAndView.setViewName(LoginController.INDEX_URL);
return modelAndView;
}
#RequestMapping("/logout")
public String logout(HttpServletRequest argReq, HttpServletResponse argResp) throws ServletException, IOException
{
HttpSession session = argReq.getSession(false);
Enumeration<?> attributeNames = session.getAttributeNames();
while(attributeNames.hasMoreElements())
{
String attrName = (String)attributeNames.nextElement();
if(session.getAttribute(attrName) != null)
{
session.setAttribute(attrName,null);
//session.removeAttribute(attrName);
attributeNames = session.getAttributeNames();
}
}
// close session
session.invalidate();
return LoginController.LOGIN_URL;
}
}
AdminController
#Controller
#RequestMapping("/AdminController")
#SessionAttributes({"myUser"})
public class AdminController
{
private static String SETTINGS_PAGE = "settings/index";
#RequestMapping("/index")
public ModelAndView index(#ModelAttribute(value = "myUser") User loggedInUser, HttpSession ses)
{
ModelAndView modelAndView = new ModelAndView();
Map<String, Object> map = new HashMap<String, Object>();
map.put("loggedInUserId", loggedInUser.getUserID());
map.put("userName", loggedInUser.getUserName());
modelAndView.addAllObjects(map);
modelAndView.setViewName(AdminController.SETTINGS_PAGE);
return modelAndView;
}
}
Remove this annotation
#SessionAttributes({"myUser"})
For starters #SessionAttributes isn't designed to store data in the session between different controllers. Its intended use is only to store data for the same controller in between requests. If you want to store items in the session between requests store them in the session yourself and don't rely on #SessionAttributes. This is also mentioned in the javadoc of the annotation (although a bit cryptic maybe).
If you want to remove object cached by #SessionAttributes you cannot simply clear the session but you would have to use the SessionStatus object (which you can add as an argument) to mark the use of these objects complete.
Your logout method is way to verbose, simply calling session.invalidate() should be enough, but I guess this was one of your attempts to fix things. Also when you are on a Servlet 3.0 container simply calling request.logout() could be enough (or call it in conjunction with session.invalidate())
My final advice would be to use Spring Security instead of trying to develop your own security solution.
I'm using Spring Security 3 and Spring MVC 3.05.
I would like to print username of currently logged in user,how can I fetch UserDetails in my Controller?
#RequestMapping(value="/index.html", method=RequestMethod.GET)
public ModelAndView indexView(){
UserDetails user = ?
mv.addObject("username", user.getUsername());
ModelAndView mv = new ModelAndView("index");
return mv;
}
If you already know for sure that the user is logged in (in your example if /index.html is protected):
UserDetails userDetails =
(UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
To first check if the user is logged in, check that the current Authentication is not a AnonymousAuthenticationToken.
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (!(auth instanceof AnonymousAuthenticationToken)) {
// userDetails = auth.getPrincipal()
}
Let Spring 3 injection take care of this.
Thanks to tsunade21 the easiest way is:
#RequestMapping(method = RequestMethod.GET)
public ModelAndView anyMethodNameGoesHere(Principal principal) {
final String loggedInUserName = principal.getName();
}
If you just want to print user name on the pages, maybe you'll like this solution. It's free from object castings and works without Spring Security too:
#RequestMapping(value = "/index.html", method = RequestMethod.GET)
public ModelAndView indexView(HttpServletRequest request) {
ModelAndView mv = new ModelAndView("index");
String userName = "not logged in"; // Any default user name
Principal principal = request.getUserPrincipal();
if (principal != null) {
userName = principal.getName();
}
mv.addObject("username", userName);
// By adding a little code (same way) you can check if user has any
// roles you need, for example:
boolean fAdmin = request.isUserInRole("ROLE_ADMIN");
mv.addObject("isAdmin", fAdmin);
return mv;
}
Note "HttpServletRequest request" parameter added.
Works fine because Spring injects it's own objects (wrappers) for HttpServletRequest, Principal etc., so you can use standard java methods to retrieve user information.
That's another solution (Spring Security 3):
public String getLoggedUser() throws Exception {
String name = SecurityContextHolder.getContext().getAuthentication().getName();
return (!name.equals("anonymousUser")) ? name : null;
}
if you are using spring security then you can get the current logged in user by
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName(); //get logged in username
You can use below code to find out principal (user email who logged in)
org.opensaml.saml2.core.impl.NameIDImpl principal =
(NameIDImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String email = principal.getValue();
This code is written on top of SAML.