How can I add my business logic to the authentication of spring cloud oauth? - spring-boot

In the oauth server side(auth microservice),I try to implement the oauth center by spring security and jwt, here is the Code of my UserDetailService:
#Override
#Transactional
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
ManagerInfo managerInfo = userMapper.getUserByLoginName(username);
if(managerInfo ==null)
throw new UsernameNotFoundException("sorry,can not find user of "+username+"!");
Collection<Role> roleList =roleMapper.getRoleByUserId(managerInfo.getId());
String roleIds = roleList.stream().map(role ->role.getId().toString()).collect(Collectors.joining(","));
Collection<Authority> authorities =authorityMapper.getAuthoritiesByRoleIds(roleIds);
CustomUserPrincipal userDetail = new CustomUserPrincipal(managerInfo.getUsername(),managerInfo.getPassword(),
authorities);
userDetail.setUser(managerInfo);
return userDetail;
}
The CustomUserprincipal just extends org.springframework.security.core.userdetails.User,and the Authority implements org.springframework.security.core.GrantedAuthority.
In my gateway project , I try to use spring boot(version:1.5.9.RELEASE) and here is some dependency:
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
Now,I have successfully authenticate the user by "prePostEnabled" way,here is the controller code:
#RequestMapping(value = "/getUserById/{id}",method = RequestMethod.GET)
#PreAuthorize("hasAnyAuthority('LIST_USER')")
CollectionAccount getUserById(#PathVariable String id){
return managerService.getCollectionAccount(Long.parseLong(id));
}
All the code I have written works great,and the user have 'LIST USER' authority which is returned by the userdetailservice can access this method.
But,what if I want to authenticate the user by resource type,not just the authority string?
Or add some business logic to the authentication, like 'A_PARK_LIST_USER','B_PARK_LIST_USER' and so on,maybe the user login in just have 'A_PARK_LIST_USER' and can not list the user of "B_PARK",so I can not control the access right just by'LIST USER', how can I do that?

You just need to inject the AuthenticationPrincipal into the controller method.
Try this:
#RequestMapping(value = "/getUserById/{id}",method = RequestMethod.GET)
#PreAuthorize("hasAnyAuthority('LIST_USER')")
CollectionAccount getUserById(#PathVariable String id, #AuthenticationPrincipal Principal principal){
//Here you have the users information, the principal.getName() will be the username that has been authenticated
log.debug(principal.getName());
return managerService.getCollectionAccount(Long.parseLong(id));
}

Related

How to retrieve attributes and username sent by the CAS server with Spring Security

I have a spring boot application, which is MVC in nature. All page of this application are being authenticated by CAS SSO.
I have used "spring-security-cas" as described at https://www.baeldung.com/spring-security-cas-sso
Everything working fine as expected. However, I have one problem - that is, I cannot retrieve attributes
and username sent by the CAS server in the following #Bean. What need I do to retrieve all the attributes
and and username sent by the CAS server?
#Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider provider = new CasAuthenticationProvider();
provider.setServiceProperties(serviceProperties());
provider.setTicketValidator(ticketValidator());
provider.setUserDetailsService(
s -> new User("casuser", "Mellon", true, true, true, true,
AuthorityUtils.createAuthorityList("ROLE_ADMIN")));
provider.setKey("CAS_PROVIDER_LOCALHOST_9000");
return provider;
}
First you will need to configure the attributeRepository source and the attributes to be retrieved, in attributeRepository section in CAS server, like:
cas.authn.attributeRepository.jdbc[0].singleRow=false
cas.authn.attributeRepository.jdbc[0].sql=SELECT * FROM USERATTRS WHERE {0}
cas.authn.attributeRepository.jdbc[0].username=username
cas.authn.attributeRepository.jdbc[0].role=role
cas.authn.attributeRepository.jdbc[0].email=email
cas.authn.attributeRepository.jdbc[0].url=jdbc:hsqldb:hsql://localhost:9001/xdb
cas.authn.attributeRepository.jdbc[0].columnMappings.attrname=attrvalue
cas.authn.attributeRepository.defaultAttributesToRelease=username,email,role
Check this example from CAS blog.
Then you need to implement an AuthenticationUserDetailsService at the service to read attributes returned from CAS authentication, something like:
#Component
public class CasUserDetailService implements AuthenticationUserDetailsService {
#Override
public UserDetails loadUserDetails(Authentication authentication) throws UsernameNotFoundException {
CasAssertionAuthenticationToken casAssertionAuthenticationToken = (CasAssertionAuthenticationToken) authentication;
AttributePrincipal principal = casAssertionAuthenticationToken.getAssertion().getPrincipal();
Map attributes = principal.getAttributes();
String uname = (String) attributes.get("username");
String email = (String) attributes.get("email");
String role = (String) attributes.get("role");
String username = authentication.getName();
Collection<SimpleGrantedAuthority> collection = new ArrayList<SimpleGrantedAuthority>();
collection.add(new SimpleGrantedAuthority(role));
return new User(username, "", collection);
}
}
Then, adjust your authenticationProvider with provider.setAuthenticationUserDetailsService(casUserDetailService);

Spring OAuth2.0: Getting User Roles based on ClientId (Authorization Code Grant Type)

I have a setup of spring boot OAuth for AuthServer and it is resposible for serving a number of few resource server for authentication using spring-security-jwt.
My problem is while authenticating I need to load the roles of a user but specific to the clientId.
eg: If user1 have roles ROLE_A, ROLE_B for client1 and ROLE_C, ROLE_D for client2, then when the user logins either using client1 or client2 he is able to see all the four roles ie. ROLE_A, ROLE_B, ROLE_C, ROLE_D because I am getting roles based on username.
If I need to have a role based on the client then I need clientId.
FYI,
I am using the authorization code flow for authentication.
I have seen similar question but that is based on password grant but I am trying on authorization code flow and that solution doesn't work for me.
Password grant question link
Below is my code where I need clientId
MyAuthenticationProvider.java
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
String userName = ((String) authentication.getPrincipal()).toLowerCase();
String password = (String) authentication.getCredentials();
String clientId = ? // how to get it
....
}
}
MyUserDetailsService.java
#Override
public UserDetails loadUserByUsername(String username) {
String clientId = ? // how to get it
....
}
}
You probably need to see OAuth2Authentication in Spring-security. When your client is authenticated by oauth2, then your "authentication" is actually instance of OAuth2Authentication that eventually implements Authentication.
If you see the implementation of OAuth2Authentication, it's done as below;
public Object getPrincipal() {
return this.userAuthentication == null ? this.storedRequest.getClientId() : this.userAuthentication
.getPrincipal();
}
so if request included "clientId', then you should be able to get clientId by calling getPrincipal() and typecasting to String as long as your request didn't include user authentication.
For your 2nd case, username is actually considered as clientId. You need to call in-memory, RDBMS, or whatever implementation that has clientId stored and returns ClientDetails. You'll be able to have some idea by looking into Spring security's ClientDetailsUserDetailsService class.
Since I didn't get any appropriate solution for my question, I am posting the solution that I used after digging source code and research.
MyJwtAccessTokenConverter.java (Extend JwtAccessTokenConverter and implement enhance method)
public class OAuthServerJwtAccessTokenConverter extends JwtAccessTokenConverter {
....
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String clientId = authentication.getOAuth2Request().getClientId();
// principal can be string or UserDetails based on whether we are generating access token or refreshing access token
Object principal = authentication.getUserAuthentication().getPrincipal();
....
}
....
}
Info:
In enhance method, we will get clientId from authentication.getOAuth2Request() and userDetails/user_name from authentication.getUserAuthentication().
Along with JwtAccessTokenConverter, AuthenticationProvider and UserDetailsService are required for authentication in generating access token step and refresh token step respectively.
get authorization header from request then parse from base64 to get the client-id.
something like this:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes())
.getRequest();
String authHeader = request
.getHeader("Authorization");

Need help testing a REST controller that requires oauth2

I am following this example for how to test my REST controller with oauth2. Testing an OAuth Secured API with Spring MVC
The code that I am stuck on is this line .with(httpBasic("fooClientIdPassword","secret"))
Does anyone know where is httpBasic method coming from? How is it instantiated, etc.? Thank you.
private String obtainAccessToken(String username, String password) throws Exception {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "password");
params.add("client_id", "fooClientIdPassword");
params.add("username", username);
params.add("password", password);
ResultActions result
= mockMvc.perform(post("/oauth/token")
.params(params)
.with(httpBasic("fooClientIdPassword","secret"))
.accept("application/json;charset=UTF-8"))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"));
String resultString = result.andReturn().getResponse().getContentAsString();
JacksonJsonParser jsonParser = new JacksonJsonParser();
return jsonParser.parseMap(resultString).get("access_token").toString();
}
The httpBasic method comes from SecurityMockMvcRequestPostProcessors
I suppose you cannot find it cause you have not imported the dependency in your project. Once you add
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
in your pom you will be able to import and use it.

How to customize the behavior of session scoped bean by current user is Spring MVC

Consider following scenario: Spring Security authenticates login data against custom UserDetailsServiceimplementation as follows
#Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
UserProfile profile = users.find(name);
if (profile.getUsername().equals("admin"))
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return new User(profile.getUsername(), profile.getPassword(), authorities);
}
If authentication succeeds, I want to create unique session scoped service in controller, with customized behavior by valid UserProfile object state. I guess best way to do that is to declare the session bean manually in configuration file and somehow autowire UserProfile or session owner to it's constructor, but how that's possible, when UserProfile is not even an managed object?
In this case, I want server to create service for authenticated user, which maintains SSH connection to remote host with credentials stored in UserProfile
Also, how to restrict a creation of such service just to post login? Is there way to achieve this kind of behavior, or is it actually bad architecture?
You can use the SecurityContextHolder to access the authenticated user for the current request. I think the best approach is to create a singleton Service with a method like this:
public UserDetails getCurrentUser() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
return (UserDetails) principal;
} else {
//handle not authenticated users
return null;
}
}
Now you can autowire and use the service in your controllers.

Spring OAuth/JWT get extra information from access token

I made a simple application that use spring security with oauth/jwt provider.
I added extra information in jwt token by custom JwtAccessTokenConverter and it works well.
My issue is how gets these extra informations in my Rest Controller.
This is my test:
#RequestMapping(value = "/test", produces = { "application/json" },method = RequestMethod.GET)
public String testMethod(OAuth2Authentication authentication,
OAuth2AccessToken token,
Principal user){
.....
Object a=token.getAdditionalInformation();
Object b=token.getValue();
...
}
The results are:
OAuth2Authentication: well inject but don't contain additional informations or accesstoken object (it contains only the original jwt token string).
User is a reference to OAuth2Authentication
OAuth2AccessToken: is aop proxy without any information infact object A and B are null.
Some extra info:
I checked,by debug, that ResourceService use my JwtAccessTokenConverter and extract the list of additional information from the access token string in input.
I found a possible solution.
I set in my JwtAccessTokenConverter a DefaultAccessTokenConverter where i set my custom UserTokenConverter.
So..
The JwtAccessTokenConverter manage only the jwt aspect of access token (token verification and extraction), the new DefaultAccessTokenConverter manages the oauth aspect of access token convertion including the use of my custom UserTokenConverter to create the Pricipal with custom informations extracted from jwt token.
public class myUserConverter extends DefaultUserAuthenticationConverter {
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME)) {
// Object principal = map.get(USERNAME);
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
UserDto utente = new UserDto();
utente.setUsername(map.get(USERNAME).toString());
utente.setUfficio(map.get("ufficio").toString());
utente.setExtraInfo(map.get("Informazione1").toString());
utente.setNome(map.get("nome").toString());
utente.setCognome(map.get("cognome").toString());
utente.setRuolo(map.get("ruolo").toString());
return new UsernamePasswordAuthenticationToken(utente, "N/A", authorities);
}
return null;
}

Resources