I have a keycloak instance which I use for CRUD operations with roles. Is there any way to get role permissions? I ve tried to search eveerything about it, but I cant find how to get permissions assigned to a role...
Here is an example of my code:
#RestController
#RequestMapping("/roles")
public class RolesController {
// must use "master" realm and "admin-cli" to connect to the instance
// although other realms and clients can be modified
private Keycloak keycloak = KeycloakBuilder.builder()
.serverUrl("http://localhost:8437/auth")
.realm("master")
.clientId("admin-cli")
.username("admin")
.password("admin")
.build();
#GetMapping
public ResponseEntity<List<RoleRepresentation>> getRoles() throws IOException {
return new ResponseEntity<>(keycloak.realm("dashing-data").roles().list(), HttpStatus.OK);
}
#PostMapping
public ResponseEntity<RoleRepresentation> createRole(#RequestBody RoleRepresentation role) throws IOException {
List<RoleRepresentation> roleList = keycloak.realm("dashing-data").roles().list();
boolean roleAlreadyExist = roleList.stream().anyMatch(r -> r.getName().contains(role.getName()));
RoleRepresentation newRole = new RoleRepresentation();
if (!roleAlreadyExist){
newRole.setName(role.getName());
newRole.setDescription(role.getDescription());
keycloak.realm("dashing-data").roles().create(newRole);
}
return new ResponseEntity<>(newRole, HttpStatus.OK);
}
#DeleteMapping("/{id}")
public ResponseEntity<String> deleteRole(#PathVariable String id){
RoleByIdResource role = keycloak.realm("dashing-data").rolesById();
if (role == null){
return new ResponseEntity<>("Could not find the role!", HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>("Role successfully deleted!", HttpStatus.OK);
}
}
I think you are adding complexity where you don't need it. A keycloak Role should be enough for what you are looking for. If you have a CRUD operation that only managers can do (like a DELETE) you simply create a Role in keycloak called "manager" and assign it to the user that you want. In your backend application, you just have to compare the role name to the required role. On the other hand for a Read operation, you can match if the user has the manager or read Role from keycloak.
Related
I am new to spring boot and testing and I have spring boot app (generated with JHipster) that uses authentication. I need to get the id of the current user.
so this method inside userRepository returns the current user
#Query(value = "select u.* from user u where u.username=?#{principal.username}", nativeQuery = true)
User findConnectedUser();
here is the method I want to test in my controller:
#PostMapping("/rdvs")
public ResponseEntity<Rdv> createRdv(#RequestBody Rdv rdv) throws URISyntaxException {
log.debug("REST request to save Rdv : {}", rdv);
if (rdv.getId() != null) {
throw new BadRequestAlertException("A new rdv cannot already have an ID", ENTITY_NAME, "idexists");
}
User user = userRepository.findConnectedUser();
rdv.setIdUser(user.getId());
Rdv result = rdvRepository.save(rdv);
return ResponseEntity
.created(new URI("/api/rdvs/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, result.getId().toString()))
.body(result);
}
here is my test method
#Autowired
private RdvRepository rdvRepository;
#Autowired
private UserRepository userRepository;
....
#Test
#Transactional
void createRdv() throws Exception {
int databaseSizeBeforeCreate = rdvRepository.findAll().size();
// Create the Rdv
restRdvMockMvc
.perform(post(ENTITY_API_URL).contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(rdv)))
.andExpect(status().isCreated());
// Validate the Rdv in the database
User user = userRepository.findConnectedUser();// this generate the NullPointer exception
List<Rdv> rdvList = rdvRepository.findAll();
assertThat(rdvList).hasSize(databaseSizeBeforeCreate + 1);
Rdv testRdv = rdvList.get(rdvList.size() - 1);
assertThat(testRdv.getDescription()).isEqualTo(DEFAULT_DESCRIPTION);
assertThat(testRdv.getIdUser()).isEqualTo(user.getId());
}
So this method generate a NullPointer, I guess because the method can't find the current user which should be authenticated first. So how can I authenticate a user inside that test method please I spend a lot of time with it but nothing seems to be working
note: I tried to call this api that authenticate users
#PostMapping("/authenticate")
public ResponseEntity<JWTToken> authorize(#Valid #RequestBody LoginVM loginVM) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
loginVM.getUsername(),
loginVM.getPassword()
);
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.createToken(authentication, loginVM.isRememberMe());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);
return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK);
}
like this in test method
User user = new User();
user.setLogin("user-jwt-controller");
user.setEmail("user-jwt-controller#example.com");
user.setActivated(true);
user.setPassword(passwordEncoder.encode("test"));
userRepository.saveAndFlush(user);
LoginVM loginVM = new LoginVM();
loginVM.setUsername("user-jwt-controller");
loginVM.setPassword("test");
//I don't know how to call the api #PostMapping("/authenticate")
Thanks in advance
Have a look at #WithMockUser annotation, see https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/test-method.html
You can see an example in the project that was generated by JHipster:
#AutoConfigureMockMvc
#WithMockUser(value = TEST_USER_LOGIN)
#IntegrationTest
class AccountResourceIT {
What I wanna achieve
So I have a client application in java (JavaFX + Spring-boot hybrid-application). You can have a look at it here https://github.com/FAForever/downlords-faf-client . So till now we stored username/ password if the user wished to be kept logged in which is obviously a pretty bad idea. So now I wanna store the refreshtoken and then log the user in with that.
What it looks like now
See here
ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails();
details.setClientId(apiProperties.getClientId());
details.setClientSecret(apiProperties.getClientSecret());
details.setClientAuthenticationScheme(AuthenticationScheme.header);
details.setAccessTokenUri(apiProperties.getBaseUrl() + OAUTH_TOKEN_PATH);
details.setUsername(username);
details.setPassword(password);
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(details);
restOperations = templateBuilder
// Base URL can be changed in login window
.rootUri(apiProperties.getBaseUrl())
.configure(restTemplate);
What I found so far
I found out that restTemplate.getAccessToken().getRefreshToken() will give me the refreshtoken I want to save and later so to keep the user logged in.
What I can not figure out
I can not find a way to create a OAuth2RestTemplate with an refresh token only. Is that even possible? Can someone point me in the right direction? Maybe link me some articles to read? Is this the right place to read?
I do not think this is possible with an OAuth2RestTemplate, but you can reimplement the desired parts yourself. I'd like to share an example with your for OAuth password login to Microsofts flavour of OAuth2 (Azure Active Directory). It does miss the piece of fetching a new token from an existing refresh token yet, but I added a comment where you need to add it.
A simple way to mimic OAuthRestTemplates behavior is a custom ClientHttpRequestInterceptor which delegates the token fetching to a dedicated Spring service component, that you append to your RestTemplate:
#RequiredArgsConstructor
#Slf4j
public class OAuthTokenInterceptor implements ClientHttpRequestInterceptor {
private final TokenService tokenService;
#NotNull
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
request.getHeaders().add("Authorization", "Bearer " + tokenService.getRefreshedToken().getValue());
return execution.execute(request, body);
}
}
This interceptor can be added to your primary RestTemplate:
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(globalOAuthTokenInterceptor);
restTemplate.setInterceptors(interceptors);
The token service used in the interceptor holds the token in a cache and on request checks for the expiry of the token and if required queries a new one.
#Service
#Slf4j
public class TokenService {
private final TokenServiceProperties tokenServiceProperties;
private final RestTemplate simpleRestTemplate;
private OAuth2AccessToken tokenCache;
public TokenService(TokenServiceProperties tokenServiceProperties) {
this.tokenServiceProperties = tokenServiceProperties;
simpleRestTemplate = new RestTemplateBuilder().
build();
}
public OAuth2AccessToken getRefreshedToken() {
if (tokenCache == null || tokenCache.isExpired()) {
log.debug("Token expired, fetching new token");
tokenCache = refreshOAuthToken();
} else {
log.debug("Token still valid for {} seconds", tokenCache.getExpiresIn());
}
return tokenCache;
}
public OAuth2AccessToken loginWithCredentials(String username, String password) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "password");
map.add("resource", tokenServiceProperties.getAadB2bResource());
map.add("client_id", tokenServiceProperties.getAadB2bClientId());
map.add("username", username);
map.add("password", password);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
return simpleRestTemplate.postForObject(
tokenServiceProperties.getAadB2bUrl(),
request,
OAuth2AccessToken.class
);
}
private OAuth2AccessToken refreshOAuthToken() {
return loginWithRefreshToken(tokenCache.getRefreshToken().getValue());
}
public OAuth2AccessToken loginWithRefreshToken(String refreshToken) {
// add code for fetching OAuth2 token from refresh token here
return null;
}
}
In this code example you would once login using username and password and afterwards all further logins would be using the refresh token. If you want to use the refresh token directly, you use the public method, otherwise it will be done internally.
Since the login code is specifically written for login to Microsoft AAD, you should recheck the MultiValueMap parameters.
TokenServiceProperties are straightforward:
#Data
public class TokenServiceProperties {
private String aadB2bUrl;
private String aadB2bClientId;
private String aadB2bResource;
}
Adapt them if needed.
The whole solution has one minor drawback: Instead of one RestTemplate that you usually fetch via depency injection, you now need a second one (a "simple" one) to fetch the OAuth token. In this example we create it in the constructor of the TokenService. However this is in general bad style as it makes it harder for unit testing etc. You could also think about using qualified beans or using a more basic http client in the TokenService.
Another important thing to note: I am using the spring-security-oauth2 package here. If you did not configure Spring Security in your project, this will trigger Spring Security auto-configuration which might not be desired - you can solve this by excluding undesired packages, e.g. in gradle:
implementation("org.springframework.security.oauth:spring-security-oauth2") {
because "We only want the OAuth2AccessToken interface + implementations without activating Spring Security"
exclude group: "org.springframework.security", module: "spring-security-web"
exclude group: "org.springframework.security", module: "spring-security-config"
exclude group: "org.springframework.security", module: "spring-security-core"
}
Spring Security 4.2.4. Java 8
I need to change the permissions of users without re-logging them. It's my service:
#Component
public class AuthoritiesUpdater {
private final UserRoleService userRoleService;
#Autowired
public AuthoritiesUpdater(UserRoleService userRoleService) {
this.userRoleService = userRoleService;
}
public void update(User user) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
List<UserRole> userRoles = userRoleService.findByUser(user);
List<GrantedAuthority> actualAuthorities = userRoles.stream().map(userRole -> new
SimpleGrantedAuthority(userRole.getRole())).collect(Collectors.toList());
Authentication newAuth = new
UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), actualAuthorities);
SecurityContextHolder.getContext().setAuthentication(newAuth);
}
}
But there is a trouble. I need to change roles for any user. Let's say I'm manager and I want to change roles for test user. I need to reload the permissions after saving configuration and test user will have new roles when he press F5 button and reload the page without re-login.
But SecurityContextHolder.getContext().getAuthentication(); returns authentication object only for current user (manager). I need to get authentication object for changed user to call new
UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), actualAuthorities);.
Can I get authentication object for any user or can I solve this problem in another way? Maybe I can get authentication object uses session registry?
P.S.: Doing an expired session for user isn't a good option.
Excellent answer, thanks #virkom!
For newer users, the steps are as follow:
Create a list of authorities you want the user to have (including the previous one(s) they had). In this case (below) I created a set so they would be unique.
Create an authentication object which matches the user you want to authenticate with their new authorities. In this case, I used a user who was in memory to simplify the example, but database authentication is preferred!
Update the security context with the new authentication information. This will have the effect of a refresh of the user's credentials.
A code snippet is shown below:
Set<GrantedAuthority> authorities = new HashSet<>();
authorities.add(new SimpleGrantedAuthority("USER"));
authorities.add(new SimpleGrantedAuthority("ADMIN"));
Authentication reAuth = new UsernamePasswordAuthenticationToken("user",new
BCryptPasswordEncoder().encode("password"),authorities);
SecurityContextHolder.getContext().setAuthentication(reAuth);
Sample code is available on github: https://github.com/aoa4eva/ContextDemo
The solution is a bit easier than I thought. I have a user.
And I can call UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword(), actualAuthorities); instead of UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(), actualAuthorities);.
I don't need to get authentication object. Final version of service:
#Component
public class AuthoritiesUpdater {
private final UserRoleService userRoleService;
#Autowired
public AuthoritiesUpdater(UserRoleService userRoleService) {
this.userRoleService = userRoleService;
}
public void update(User user) {
List<UserRole> userRoles = userRoleService.findByUser(user);
List<GrantedAuthority> actualAuthorities = userRoles.stream().map(userRole -> new SimpleGrantedAuthority(userRole.getRole())).collect(Collectors.toList());
Authentication newAuth = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), actualAuthorities);
SecurityContextHolder.getContext().setAuthentication(newAuth);
}
}
I have configured the server.xml file in a Java Spring application to authenticate users when logging in from database tables and roles. I'm wondering how in Java code can I check who's logged in to the application?
I know that in a jsp file I can just use this following syntax to show the name:
${pageContext.request.userPrincipal.name} .
In your Spring MVC Controller, just add the following statement:
String loggedUser = request.getUserPrincipal().getName();
where request is the object of HttpRequest type, made available to you by Spring on demand.
There is very beautiful article for this is given at http://www.baeldung.com/spring-security-track-logged-in-users
You can leverage the HttpSessionBindingListener to update the list of logged in users whenever user information is added to the session or removed from the session based on user logs into the system or logs out from the system.
It will listen to events of type HttpSessionBindingEvent, which are triggered whenever a value is set or removed, or, in other words, bound or unbound, to the HTTP session.
#Component
public class LoggedUser implements HttpSessionBindingListener {
private String username;
private ActiveUserStore activeUserStore;
public LoggedUser(String username, ActiveUserStore activeUserStore) {
this.username = username;
this.activeUserStore = activeUserStore;
}
public LoggedUser() {}
#Override
public void valueBound(HttpSessionBindingEvent event) {
List<String> users = activeUserStore.getUsers();
LoggedUser user = (LoggedUser) event.getValue();
if (!users.contains(user.getUsername())) {
users.add(user.getUsername());
}
}
#Override
public void valueUnbound(HttpSessionBindingEvent event) {
List<String> users = activeUserStore.getUsers();
LoggedUser user = (LoggedUser) event.getValue();
if (users.contains(user.getUsername())) {
users.remove(user.getUsername());
}
}
// standard getter and setter
}
You can go through the whole code here
You can also retrieve the current logged in user from Spring security
Go through this artical
Or through Request also
request.getUserPrincipal().getName();
You can write a method to get current logged in user as you might need this various places like below :
public User getCurrentLoggedInUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
Object principal = auth.getPrincipal();
if (principal instanceof User) {
return ((User) principal);
}
}
}
I've started to use JHipster weeks ago and everything went find since now. I want to have a LDAP authentification with at the same time the default authentification of JHipster.
I followed this https://jhipster.github.io/tips/016_tip_ldap_authentication.html and it doesn't work as planned.
Actually my configuration is connecting well to my LDAP server and i know by viewing logs that the login search into the LDAP server and compare the password.
The problem is the login fail with the error :
UT005023: Exception handling request to /api/authentication
org.springframework.security.core.userdetails.UsernameNotFoundException: User nseys was not found in the database
at com.mycompany.myapp.security.PersistentTokenRememberMeServices.lambda$onLoginSuccess$1(PersistentTokenRememberMeServices.java:116)
at java.util.Optional.orElseThrow(Optional.java:290)
at com.mycompany.myapp.security.PersistentTokenRememberMeServices.onLoginSuccess(PersistentTokenRememberMeServices.java:116)
at org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices.loginSuccess(AbstractRememberMeServices.java:294)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
...
The thing is I want JHipster to automatically create the user in database when it doesn't exist in there with a mapping of parameters (but only when it's a LDAP user) and just connect if it's already done.
I've searched Spring-security solution aswell but the implementations are too far away from the initial files created by JHipster and I don't want to destroy all this.
Well I tried something that work, I don't know if this is how I should have done, but since I've found nothing about that, and it's not documented alot, I'll stick with that solution unless I find a better solution.
// PersistentTokenRememberMeServices.java
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication
successfulAuthentication) {
String login = successfulAuthentication.getName();
log.debug("Creating new persistent login for user {}", login);
PersistentToken t = new PersistentToken();
t.setSeries(RandomUtil.generateSeriesData());
t.setTokenValue(RandomUtil.generateTokenData());
t.setTokenDate(LocalDate.now());
t.setIpAddress(request.getRemoteAddr());
t.setUserAgent(request.getHeader("User-Agent"));
PersistentToken token = userRepository.findOneByLogin(login).map(u -> {
t.setUser(u);
return t;
}).orElse(null);
if (token == null) {
if (successfulAuthentication.getPrincipal() instanceof LdapUserDetails) {
User ldapUser = new User();
ldapUser.setLogin(login);
ldapUser.setPassword(RandomStringUtils.random(60)); // We use LDAP password, but the password need to be set
ldapUser.setActivated(true);
CustomLdapUserDetails customLdapUserDetails = (CustomLdapUserDetails) successfulAuthentication.getPrincipal();
ldapUser.setEmail(customLdapUserDetails.getEmail());
ldapUser.setFirstName(customLdapUserDetails.getFirstName());
ldapUser.setLastName(customLdapUserDetails.getLastName());
Set<Authority> authorities = new HashSet<>();
authorities.add(this.authorityRepository.findOneByName("ROLE_USER"));
ldapUser.setAuthorities(authorities);
ldapUser.setLangKey("fr");
userRepository.save(ldapUser);
t.setUser(ldapUser);
token = t;
} else {
throw new UsernameNotFoundException("User " + login + " was not found in the database");
}
}
...
}
And I added a contextMapper to get the attributes in the LDAP server
// SecurityConfiguration.java
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
return new LdapUserDetailsMapper() {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
UserDetails details = super.mapUserFromContext(ctx, username, authorities);
return new CustomLdapUserDetails((LdapUserDetails) details, ctx);
}
};
}
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl(applicationProperties.getLdap().getUrl());
contextSource.setBase(applicationProperties.getLdap().getBase());
contextSource.setUserDn(applicationProperties.getLdap().getUserDn());
contextSource.setPassword(applicationProperties.getLdap().getPassword());
contextSource.afterPropertiesSet(); //needed otherwise you will have a NullPointerException in spring
auth.ldapAuthentication()
.userDetailsContextMapper(userDetailsContextMapper())
.userSearchBase(applicationProperties.getLdap().getSearchBase()) //don't add the base
.userSearchFilter(applicationProperties.getLdap().getSearchFilter())
.contextSource(contextSource)
;
}