I'm trying to implement custom auth flow in Keycloak. It should work similar to username&password flow (POST /openid-connect/token with params 'username'+'password'+'grant_type=password' -> response with access_token & refresh_token) but instead of username and password it will receive another fields (e.g. fieldA, filedB and hash)
I wrote an implementation of
org.keycloak.authentication.Authenticator
that does this auth, but I can't figure out what should I do next. How can I make keycloak validate user using this flow?
So If I understand you correctly: U have a custom implementation of the Authenticator interface, to register it in keyckoak you also need AuthenticatorFactory - implementation and add the path to it into the config file with the name: org.keycloak.authentication.AuthenticatorFactory. So you should have something like:
public class CustomAuthenticator implements Authenticator {
#Override
public void authenticate(AuthenticationFlowContext context) {
//todo make your auth validation check logic
String username = "admin";
UserModel user = context.getSession().users().getUserByUsername(username, context.getRealm());
context.setUser(user);
context.success(); // With context.success(), or failing with an error, you can manage your custom validation
}
}
public class CustomAuthenticatorFactory implements AuthenticatorFactory {
public static final String PROVIDER_ID = "custom-sso";
private static CustomAuthenticator SINGLETON = new CustomAuthenticator();
#Override
public String getDisplayType() {
return "custom sso";
}
#Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;
}
#Override
public Authenticator create(KeycloakSession session) {
return SINGLETON;
}
#Override
public String getId() {
return PROVIDER_ID;
}
}
And also in file with path: src\main\resources\META-INF\services\org.keycloak.authentication.AuthenticatorFactory
need to add a path to the factory class.
After all these changes, you should be able to change your authentication flow from keyclaok admin page.
P.S. you cant change existed Browser flow, but you can copy it, change the copy and then bind the browser flow to your custom.
P.S.S.
I found almost the same question: Keycloak adding new authenticator
But with a better-described answer :-)
Related
Is it possible to define multiple authenticationProviders in Micronaut?
Let's say I have an entity A which can be logged using authenticationProviderA: which given a user and pass checks the DB table of A.
Is it possible to add an entity B and its authenticationProviderB which given a user and pass will check the DB table of B?
If so, how do you define in your controller which authenticationProvider you want to use?
After taking a look at io.micronaut.security.authentication.Authenticator I've seen it's possible to have multiple authenticationProviders in Micronaut.
The documentation says:
An Authenticator operates on several {#link AuthenticationProvider} instances returning the first authenticated {#link AuthenticationResponse}.
From what I've seen you just have to implement AuthenticationProvider and the Authenticator will include the implementations (even if it isn't annotated!) in an internal list of AuthenticationProviders.
IMHO this isn't a good way to provide multiple ways to authenticate. In the example provided in the question, the authentication for A and B both require calls to DB which means depending on the order of the execution of the AuthenticationProviders unneeded BD calls will be executed.
I think would be better to provide a way to indicate which AuthenticationProviders has to be used by controller or endpoint.
Maybe there is a way to do that and I just don't know, so feel free to comment if so.
There is no built-in solution regarding the problem, but there are multiple ways to achieve what you want with small amount of code.
Solution №1:
Creating custom AuthenticationRequest.class and LoginController.class if you need multiple login end points:
public class AuthenticationRequestForEntityA extends UsernamePasswordCredentials { ... }
public class AuthenticationRequestForEntityB extends UsernamePasswordCredentials { ... }
in your custom LoginController replace the default UsernamePasswordCredentials with your specific AuthenticationRequestForEntityA or AuthenticationRequestForEntityB and copy paste the rest of the code from the original LoginController.class:
#Consumes({MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON})
#Post
public Single<MutableHttpResponse<?>> login(#Valid #Body AuthenticationRequestForEntityA usernamePasswordCredentials, HttpRequest<?> request) {
Flowable<AuthenticationResponse> authenticationResponseFlowable = Flowable.fromPublisher(authenticator.authenticate(request, usernamePasswordCredentials));
...
and then in your authentication providers:
public class AuthenticationProviderA implements AuthenticationProvider {
#Override
public Publisher<AuthenticationResponse> authenticate(#Nullable HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
if (authenticationRequest instanceof AuthenticationRequestForEntityA) {
return authenticate(authenticationRequest);
} else {
// return empty
}
}
}
public class AuthenticationProviderB implements AuthenticationProvider {
#Override
public Publisher<AuthenticationResponse> authenticate(#Nullable HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
if (authenticationRequest instanceof AuthenticationRequestForEntityB) {
return authenticate(authenticationRequest);
} else {
// return empty
}
}
}
Solution №2: Creating your custom route-based AuthenticationProvider
Since HttpRequest is available in the AuthenticationProvider as an input parameter you can simply authenticate based on the httpRequest path or query parameter properties.
To make the code cleaner you can create your own RouteBasedAuthenticationProvider interface:
public interface RequestBasedAuthenticationProvider extends AuthenticationProvider {
/**
You can check the request path or request parameter or whatever
*/
boolean supports(HttpRequest<?> request);
}
then in Micronaut AuthenticationProvider:
#Context
public class AppAuthenticationProvider implements AuthenticationProvider {
private final Collection<RequestBasedAuthenticationProvider> providers;
constructor(...) {...}
#Override
public Publisher<AuthenticationResponse> authenticate(#Nullable HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
return providers.stream()
.filter(provider -> provider.supports(httpRequest))
.findFirst()
.orElseThrow(//Throw provider not found error)
.authenticate(httpRequest, authenticationRequest);
}
}
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!
I am creating a web service using Jersey + Jetty + Dropwizard + Hibernate.
Let's say I got a web resource like this:
#Path("/")
#PermitAll
public class Resource {
#RolesAllowed("user")
#GET
public String get() { return "GET"; }
#RolesAllowed("admin")
#POST
public String post(String content) { return content; }
#Path("sub")
public SubResource getSubResource() {
return new SubResource();
}
}
I know you can check a user's role with HttpServletRequest.isUserInRole.
The question is, how do I assign the roles to begin with? How does Jersey knows what to return for the isUserInRole method or knows how to filter people from not getting to specific resources based on their roles?
I do not have a web.xml or a webdefault.xml so the definitions should be done somewhere else.
You have to provide a provider class which supports the #RolesAllowed("admin") annotation and then you have to register the provider class in your application .Normally it is done in web.xml but as you are not using it,you have to provide it on your own.The related example could be find here
Jersey uses its own mechanism to check roles (see SecurityContext interface), however if it is not explicitly set, it falls back to container authentication, which is usually implemented using JaaS. If you had web.xml you would be able to configure basics of it there.
Then again frequently authentication is done differently. If you can log user in manually, for instance have a service that can check name/password, then you could implement a ContainerRequestFilter that would fill security context which would make #RolesAllowed annotation to just work.
For instance:
Updated based on comments
Resouce method logs in user:
#Path("some/login/path")
public void login() {
String name = getUsernameFromRequest();
String password = // like above
User user = loginService.login(name, password);
request.getSession().addAttribute("user", user);
}
Initialize security context in filter for all requests.
#Priority(Priorities.AUTHENTICATION)
public class JerseySecurityContextFilter implements ContainerRequestFilter {
public void filter(final ContainerRequestContext context) throws IOException {
User user = getUserFromSession();
if (user != null) {
// this sets security context for the rest of processing
// it should make #RolesAllowed work
context.setSecurityContext(new JerseySecurityContext(user);
}
}
final class JerseySecurityContext implements SecurityContext {
private final User user;
public JerseySecurityContext(final User user) {
this.user = user;
}
public boolean isUserInRole(final String roleName) {
return user.hasRole(roleName);
}
public Principal getUserPrincipal() {
return new Principal() {
public String getName() {
return user.getName();
}
};
}
public String getAuthenticationScheme() {
return null;
}
}
If you would rather use JaaS then you would have to configure security in your application configuration. These are some examples for embedded Jetty.
people.
I need implement authenticating method that accept username or email in form login.
I suspect that is the implementation PasswordCredentialHandler and its AbstractCredentialHandler abstract class.
Has anyone ever experienced this?
UPDATE 1
Create a handler based on class PasswordCredentialHandler and Credential based on UsernamePasswordCredential. Add new Handler in store configuration:
#Produces
IdentityConfiguration produceIdentityManagementConfiguration() {
IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder();
builder
.named("default")
.stores()
.jpa().addCredentialHandler(EmailPasswordCredentialHandler.class)
.supportAllFeatures().supportType(UserAccount.class);
return builder.build();
Use a Multiple Authenticator Support or a Custom Authenticator.
In Authentication add logic to identify if user typed username or email, then instantiates a correct Credentials.
ORIGINAL
See this official example (from picketlink site):
#RequestScoped
#Named
public class AuthenticatorSelector {
#Inject Instance<CustomAuthenticator> customAuthenticator;
#Inject Instance<IdmAuthenticator> idmAuthenticator;
private String authenticator;
public String getAuthenticator() {
return authenticator;
}
public void setAuthenticator(String authenticator) {
this.authenticator = authenticator;
}
#Produces
#PicketLink
public Authenticator selectAuthenticator() {
if ("custom".equals(authenticator)) {
return customAuthenticator.get();
} else {
return idmAuthenticator.get();
}
}
}
You can implement your logic to choose login per email or userid in selectAutheticator method and then #Produces and #Picketlink annotation provides to framework your custom authenticator (email login) or idm authenticator (ordinary userId login).
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!