I'm implementing OAuth2 for my REST Service (password grant type) with help of Spring security module. I' using postgreSQL as my Token Store. All works fine, but I need to add the possibility to change user password. If user change his password old token should be deleted/forgotten.
I implement this feature using JdbcTokenStore Spring service:
public void updatePassword(User user, String newPassword) {
...
// Update password in database
clearUserTokens(user.getUsername());
}
private void clearUserTokens(String userName) {
Collection<OAuth2AccessToken> tokens = jdbcTokenStore.findTokensByUserName(userName);
tokens.stream().forEach(jdbcTokenStore::removeAccessToken);
}
Is this approach correct? Is there any standard way of handling that kind of situations?
Related
I'm trying to implement multi-factor authentication (mfa) with spring oauth2. I have my own authorization server and I'm trying to follow this guide https://sultanov.dev/blog/multi-factor-authentication-with-spring-boot-and-oauth2. I made two custom granter CustomePasswordTokenGranter and MFATokenGranter. CustomePasswordTokenGranter will return access_token with authority pre_auth to be used with MFATokenGranter later.
CustomPasswordTokenGranter -> username, password -> Oauth2AccessToken(authorities = [PRE_AUTH])
MFATokenGranter -> access_token -> do I need to check wheter it contains PRE_AUTH or not?
public class MfaTokenGranter extends AbstractTokenGranter {
#Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
// do I need this line of code?
if(!client.getAuthorities().contains(PRE_AUTH)) {
throw new InvalidAuthorityException();
}
}
}
btw I'm not sure if I have to check authority of a client that trying to access this granter. I want the token that was generated from CustomePasswordTokenGranter to be used with MfaTokenGranter only. Does spring automatically check that for me or I have to do it my own or it doesn't matter if I check it or not?
I'm trying to configure a Spring Boot application with Keycloak to have an endpoint that is both accessible for authenticated and unauthenticated users. For authenticated users, I want to return some extra information. Here is a simple example of what I'm trying to achieve:
#RestController
public class HelloController {
#GetMapping("/")
public String index(Principal principal) {
KeycloakPrincipal keycloakPrincipal = (KeycloakPrincipal) principal;
if (keycloakPrincipal != null) {
return "Hello " + keycloakPrincipal.getKeycloakSecurityContext().getToken().getPreferredUsername();
} else {
return "Hello";
}
}
}
application.properties:
keycloak.securityConstraints[0].authRoles[0] = *
keycloak.securityConstraints[0].securityCollections[0].name = Hello
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /*
So far, I only got it to work for one of both cases. If I protect the endpoint using the security constraint above, the endpoint is only accessible to authenticated users. If I remove the security constraint, the endpoint is accessible for everyone, but then the principal will always be null.
Is it possible to achieve the intended behavior?
Have you tried something like Principal principal = SecurityContextHolder.getContext().getAuthentication();?
I believe the Principal as method parameter is only populated on secured endpoints but am unsure if it would exist in the SecurityContext. If not, you need to add a Filter to add it yourself.
I was able to solve the problem by calling the authenticate() method on the HttpServletRequest object. This will trigger the authentication process and will populate the user principal whenever possible. From the docs:
Triggers the same authentication process as would be triggered if the
request is for a resource that is protected by a security constraint.
To avoid triggering an authentication challenge, I pass in a dummy response object to the authenticate() call.
In SingleSignOn(SSO), i am getting jwt beareer token from my office network when application loads, basically i have written code in the rest web-service to get bearer token from different web-service.
What i want is:
I want to secure some of the rest endpoints in spring? How can i do that!
I don't want to provide login & password forms to re-login. Because already i am calling web-service to get the user-Info and bearer token. It means i am already login, and i am able to print user info.
I have user role table with userId, i just want to apply thoese roles available in the table to logged in User Id.
How can we do thia?
I found this but i am expecting this should not ask to re enter username and password again https://www.ekiras.com/2016/04/authenticate-user-with-custom-user-details-service-in-spring-security.html
https://stackoverflow.com/a/11314388/1684778
similar kind of my problem
https://stackoverflow.com/a/12057327/1684778
There are multiple things to consider here
User authentication: When the user credentials are verified, we mark the user authenticated and register it in SecurityContext
You must be using AuthenticationProvider to authenticate the token received.
Ex:
public class SSOAuthenticationProvider implements AuthenticationProvider {
public Authentication authenticate(Authentication authentication) {
// verified the authentication token
authentication.setAuthenticated(true);
// make the database call, get roles for the user
authentication.setAuthorities(<authorities - discussed below>);
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}
}
Authorities: Once the user is authenticated, we need to get the granted authorities for the user and set them. For that, we need a custom class to represent the granted authorities.
public class CustomAuthority implements GrantedAuthority {
private String role;
public CustomAuthority(String role) {
this.role = role;
}
#Override
public String getAuthority() {
return role;
}
}
We create these custom authority instances from the roles we receive from the database. So in previous SSOAuthenticationProvider, we do the following
// make the database call, get roles for the user
List<String> roles = <db call to get roles>
authentication.setAuthorities(Collections.unmodifiableCollection(roles.stream().map(CustomAuthority::new).collect(Collectors.toList()));
SecurityContextHolder.getContext().setAuthentication(authentication);
This results in currently authenticated user holding all the roles they are entitled to.
The only pending step is to hardcode the authority/role for each endpoint. Now, when the user authentication process is done and an endpoint verification is performed, Spring looks at the authorities needed for the current endpoint and looks for them in the currently authenticated user. If it's present, the endpoint code gets executed. If not, AccessDeniedException is thrown
I am trying to add OAuth to a rest service that I am developing with Spring framework. I am using annotation based configuration and spring-boot to get it running.
I have the following class in my project:
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecuritySettings extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("123").authorities("ROLE_USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and().httpBasic().and().csrf().disable();
}
}
and my authorization server configuration is as follows:
#Configuration
#EnableAuthorizationServer
public static class MyAuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("web")
.authorizedGrantTypes("password")
.authorities("ROLE_CLIENT","ROLE_TRUSTED_CLIENT","ROLE_USER")
.scopes("read", "write")
.resourceIds(RESOURCE_ID);
}
}
When I make a GET request to /oauth/token/ end point I am asked to enter HTTP basic credentials. When I try to login with the admin user then the following is logged
o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: NoSuchClientException, No client with requested id: admin
Entering username as web works, but I don't know the password for it. A default password is logged but it doesn't work either.
Using default security password: f23087f8-58ce-e3d-bc62-58bf0963e75c
So what is this password? Where can I find it? How can I set it?
The API you are using is from this builder class.
The token endpoint is used by client applications to request access tokens for resources. It isn't used by browser end users. OAuth2 clients are usually allocated a "client secret" which they can use to authenticate at the endpoint, generally with Basic authentication as described in the OAuth 2.0 spec.
So to answer your specific question, you would use the "secret" method on the builder API, and use the value to authenticate as the client:
clients.inMemory().withClient("web")
.authorizedGrantTypes("password")
.secret("webclientsecret")
...
Also, the "password" grant means that the client requests tokens using an end users ID and password, just to make sure that's what you actually intend. It's not related to the password issue here.
This is the OAuth access token. It is based on user login and password and used to access protected resources.
URL "/oauth/token" is used to fetch access tokens instead of available Request Token. This request is digitally signed on the basis of Request Token secret.
The Oauth protocol uses this access tokens in this way:
Application-Consumer gets Request Token.
User is redirected on the Service Provider's site and authorizes Request Token there. (If authorization is made via Http basic, then you should add request header with name "Authorization" and value "Basic EncodeBase64("name:password")", where EncodeBase64 is a function, "name" and "password" are user name and user password.
Application-Consumer exchanges Request Token on Access Token.
Application-Consumer sends authorized requests to the service's API.
You can't find additional info in OAuth 2 Developers Guide and Spring Social Reference
I hope you've got answer to your question(or get closer to it). =)
I followed the instructions to create a custom security realm for my glassfish. It all works fine, users are authenticated correctly. The problem however is the following:
The user credentials are encrypted in a string
The realm decrypts this string and performs the authentication against a database (works)
Instead of using the decrypted values as principal in the securityContext the encrypted
String is passed.
I already tried to override the commit() method to replace the _userPrincipal or attach my own implementation using getSubject().getPrincipals().add(new PrincipalImpl("user")). Neither was working as expected. Basically the question is a simple as this: How can I set my own principal in a custom security realm in glassfish in a way which makes it possible to use it together with an injected securityContext?
My environment:
Glassfish 3.1.2.2 (Build 5) Full Profile
The application running behind the authentication is a JAX-RS 1.1 based application
The SecurityContext is obtained using injection
I already tried to override the commit() method to replace the
_userPrincipal or attach my own implementation using getSubject().getPrincipals().add(new PrincipalImpl("user")). Neither
was working as expected.
What kind of error(s) do you get?
Regardless, I think your issue lies on the third step of this process. SecurityContext only defines BASIC_AUTH, FORM_AUTH, CLIENT_CERT_AUTH, DIGEST_AUTH as AuthenticationScheme so perhaps SecurityContext cannot see your implementation of your security scheme or type. But you can try these steps and I hope they would work for you.
A- Implement a Java Authentication and Authorization Service (JAAS) LoginModule or extend com.sun.appserv.security.AppservPasswordLoginModule
public class MyLoginModule extends AppservPasswordLoginModule {
#Override
protected void authenticateUser() throws LoginException {
if (!authenticate(_username, _password)) {
//Login fails
throw new LoginException("LoginFailed");
}
String[] myGroups = getGroupNames(_username);
commitUserAuthentication(myGroups);
}
private boolean authenticate(String username, String password) {
/*
Check the credentials against the authentication source, return true if authenticated, return false otherwise
*/
return true;
}
private String[] getGroupNames(String username) {
// Return the list of groups this user belongs to.
}
B- Implementing your realm class.
public class MyRealm extends AppservRealm {
#Override
public void init(Properties props)
throws BadRealmException, NoSuchRealmException {
//here you initialize the realm
}
#Override
public String getAuthType() {
return "Custom Realm";
}
}
C- Installing and configuring the realm and LoginModule into the server.
for this you need to look at JSR 196 and write you own SAM by implmenting javax.security.auth.message.module.ServerAuthModule. Take a look at thelink below.
https://blogs.oracle.com/enterprisetechtips/entry/adding_authentication_mechanisms_to_the