How to use a Custom Principal in a custom security realm (Glassfish)? - java

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

Related

Can I mix both basic authentication and JWT token authentication to protect APIs of a single Spring Boot project?

I am pretty new in Spring Security and I am working on a Spring Boot project that uses Basic Authentication in order to protect some APIs. I am starting from an existing tutorial code (a Udemy course) trying to adapt it to my own use cases.
In this project I have this SecurityConfiguration used to configure the basic authentication.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
private static String REALM = "REAME";
private static final String[] USER_MATCHER = { "/api/utenti/cerca/**"};
private static final String[] ADMIN_MATCHER = { "/api/utenti/inserisci/**", "/api/utenti/elimina/**" };
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable()
.authorizeRequests()
.antMatchers(USER_MATCHER).hasAnyRole("USER")
.antMatchers(ADMIN_MATCHER).hasAnyRole("ADMIN")
.anyRequest().authenticated()
.and()
.httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint()).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public AuthEntryPoint getBasicAuthEntryPoint()
{
return new AuthEntryPoint();
}
/* To allow Pre-flight [OPTIONS] request from browser */
#Override
public void configure(WebSecurity web)
{
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
#Bean
public BCryptPasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
};
#Bean
#Override
public UserDetailsService userDetailsService()
{
UserBuilder users = User.builder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users
.username("ReadUser")
.password(new BCryptPasswordEncoder().encode("BimBumBam_2018"))
.roles("USER").build());
manager.createUser(users
.username("Admin")
.password(new BCryptPasswordEncoder().encode("MagicaBula_2018"))
.roles("USER", "ADMIN").build());
return manager;
}
}
So from what I have understand:
Here it id defined the list of API that can be accessed by a nornmal user and the list of API that can be accessed by and admin user:
private static final String[] USER_MATCHER = { "/api/utenti/cerca/**"};
private static final String[] ADMIN_MATCHER = { "/api/utenti/inserisci/**", "/api/utenti/elimina/**" };
Into the previous configure() method basically it is stating that the API URL matching with the USER_MATCHER are accessible by logged user having role USER while API having URL matching ADMIN_MATCHER are accessible by logged user having role ADMIN. Is this interpretation correct?
Finnally the UserDetailsService bean simply define two users: one belonging to the USER "group" and the other one belonging to both the USER and ADMIN "group".
So, if I well understood, the first one will be aple only to access to the API having enpoint URL /api/utenti/cerca/** while the second one will be able to access also to the APIs having endpoint URLs /api/utenti/inserisci/** and /api/utenti/elimina/**
Is it my reasoning correct?
And now my doubt: into a controller class of this project I defined this method:
#RestController
#RequestMapping("api/users")
#Log
public class UserController {
#Autowired
UserService userService;
//#Autowired
//private BCryptPasswordEncoder passwordEncoder;
//#Autowired
//private ResourceBundleMessageSource errMessage;
#GetMapping(value = "/test", produces = "application/json")
public ResponseEntity<String> getTest() throws NotFoundException {
log.info(String.format("****** getTest() START *******"));
return new ResponseEntity<String>("TEST", HttpStatus.OK);
}
..............................................................................................................
..............................................................................................................
..............................................................................................................
}
As you can see this method handling a GET request toward the localhost:8019/api/users/test endpoint.
This endpoint URL is not in any of the previous two list related the protected endpoint (it is not into the USER_MATCHER list neither into the ADMIN_MATCHER list. So I expected that simply this endpoint was not protected and accessible to everyone. But performing the previous request using PostMan, I obtain this error message:
HTTP Status 401 : Full authentication is required to access this resource
So basically it seems to me that also if this endpoint not belong to any protected endpoint list it is in some way protected anyway (it seems to me that at least the user must be authenticated (infact trying both the previous user I can obtain the expected output, so it should mean that the endpoint is not protected by the user rule but it is protected againts not authenticated access).
Why? Maybe it depende by the previous configure() method settings, in particular this line?
.anyRequest().authenticated()
In case is it possible to disable in some way to implement something like this:
If a called endpoint belong to one of the previous two lists (USER_MATCHER and ADMIN_MATCHER) --> the user must be authenticated and need to have the correct role.
If a called endpoint not belong to one of the previous lists --> everybody can access, also not authenticated user.
This approach make sense or am I loosing something?
I take this occasion to ask you also another information: do you think that it is possible to configure Spring security of this specific project in order to protect some specific endpoints using the basic authentication and some other specific endpoints using the JWT authentication.
Sone further notes to explain why this last question. This project is a microservice that at the moment is used by another microservice (used to generate JWT token) in order to obtain user information. (the other microservice call an API of this project in order to receive user information so it can generate a JWT token that will be used in my application. The comunication between these 2 microservice must use basic authentication).
Since this project contains all the entity classes used to map the tables related to the users on my DB, my idea was to use this project also for generic user management, so it could include functionality like: add a brand new user, changes information of an existing user, obtain the list of all the users, search a specific user, and so on.
These new APIs will be protected by JWT token because each API can be called from a specific user type having different privileges on the system.
So I am asking if in a situation like this I can add without problem 2 different types of authentication (basic authentication for the API that retrieve a user so the other microservice can obtain this info) and JWT authentication for all the other APIs. It make sense or is it better to create a brand new project for a new user management microservice?
So, if I well understood, the first one will be aple only to access to the API having enpoint URL /api/utenti/cerca/** while the second one will be able to access also to the APIs having endpoint URLs /api/utenti/inserisci/** and /api/utenti/elimina/**
Yes.
Why? Maybe it depende by the previous configure() method settings, in particular this line?
Yes, when using .anyRequest().authenticated(), any requests that have not been matched will have to be authenticated.
If a called endpoint not belong to one of the previous lists --> everybody can access, also not authenticated user.
You can achieve this by doing anyRequest().permitAll(). But this is not so secure because you are allowing access to every other endpoints, instead you should stay with anyRequest().authenticated() and allow access to specific endpoints manually, like so:
http
.authorizeRequests()
.antMatchers(USER_MATCHER).hasAnyRole("USER")
.antMatchers(ADMIN_MATCHER).hasAnyRole("ADMIN")
.antMatchers("/api/users/test").permitAll()
.anyRequest().authenticated()
...

Spring Security - Public pages redirected to login with invalid session id

I came across an Issue where public urls won't work in Spring security, when you already have an SessionID which is not valid anymore.
Example:
I have the user-register page and gave it a permitAll access like so:
http.authorizeRequests().antMatchers("/register**").permitAll();
http.authorizeRequests().anyRequest().authenticated();
http.formLogin().loginPage("/login").permitAll();
For my Session settings i have:
http.sessionManagement().invalidSessionUrl("/login?logoutcause=sessiontimeout");
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
http.sessionManagement().sessionAuthenticationErrorUrl("/login");
http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);
http.sessionManagement().sessionFixation().newSession();
If i have a sessionID from a previous session, which is maybe an old and invalid one, and i hit the Route "/register", spring complains about the invalid session ID and redirects me to "/login".
Just to mention it: Everything else, like login, ressource management, protected urls and logout is working as expected with the configuration.
Reproducing this: Use Redis-Session management in Spring. Got to login page, flush redis db with console. Access the register page directly in browser -> redirected to login because of invalid session id.
o.s.s.w.s.SessionManagementFilter : Requested session ID 8ad2e166-bc21-4646-8390-ad8d1043baec is invalid.
w.s.SimpleRedirectInvalidSessionStrategy : Starting new session (if required) and redirecting to '/login?logoutcause=sessiontimeout'
o.s.s.w.DefaultRedirectStrategy : Redirecting to '/login?logoutcause=sessiontimeout'
Why does Spring even check the session id for a route that have "public" access?
The next Step:
if i fully disable any security checks on the route itself, sadly the required ressources like js and css assets trigger the same behavior and either i get redirected to login, or the assets simply do not get delivered (both no option :D )
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/register/**");
super.configure(web);
}
My Solution and Workaround
I disabled the following config which solved all my problems
// DISABLED
// http.sessionManagement().invalidSessionUrl("/login?logoutcause=sessiontimeout");
My Question
This can not be the best way to do it, right?
What is the better and more secure way to do it.
Please help me to understand why this is done this way by spring, or what i configured the wrong way.
I have solved the same issue by adding the following config:
.antMatchers("/login-invalid").permitAll()
I had same issue, and solved like below.
0. pre-requisite
jdk 17
spring-boot 2.7
1. create custom InvalidSessionStrategy
#Component
public class MyInvalidSessionStrategy implements InvalidSessionStrategy {
private final InvalidSessionStrategy simpleRedirectStrategy;
private final InvalidSessionStrategy requestedUrlRedirectStrategy;
private final HandlerMappingIntrospector handlerMappingIntrospector;
public MyInvalidSessionStrategy(HandlerMappingIntrospector handlerMappingIntrospector) {
this.simpleRedirectInvalidSessionStrategy = new SimpleRedirectInvalidSessionStrategy("/login?logoutcause=sessiontimeout");
this.requestedUrlRedirectStrategy = new RequestedUrlRedirectInvalidSessionStrategy();
this.handlerMappingIntrospector = handlerMappingIntrospector;
}
#Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
throws IOException {
var matcher = new MvcRequestMatcher(handlerMappingIntrospector, "/register/**");
if (matcher.matches(request))) {
requestedUrlRedirectStrategy.onInvalidSessionDetected(request, response);
} else {
simpleRedirectStrategy.onInvalidSessionDetected(request, response);
}
}
}
2. configure spring-securiry and register custom InvalidSessionStrategy
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
SecurityFilterChain filterChain(
HttpSecurity http,
MyInvalidSessionStrategy invalidSessionStrategy)
throws Exception {
return http
.login(...)
.logout(...)
.sessionManagement(
configurer -> configurer
.invalidSessionStrategy(invalidSessionStrategy))
.build();
}
}

Using a JWT token with dropwizard? I already have db auth, but am confused about utilizing tokens

I have the following Authenticator class for DB authentication:
public class DBAuthentication implements Authenticator<BasicCredentials, User> {
private UserDAO userDAO;
public DBAuthentication(UserDAO userDAO) {
this.userDAO = userDAO;
}
#Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException{
return userDAO.findByUsernamePassword(credentials.getUsername(), credentials.getPassword());
}
}
and then when I want to authenticate against a resource, I simply do :
#GET
#Produces(MediaType.TEXT_PLAIN)
#Path("/secured")
#UnitOfWork
public String aliveSecure(#Auth User user)
{
return "working.";
}
Which is a simple, authenticated method - and that works well... However, let's assume I want to have a user sign in, then get a token they can use for future requests, until the token expires... I would ASSUME (and correct me if I am wrong) that I would do something like have a resource, which would take the credentials, then return the token inside a response, for storage on the client end - and that is fine... but if I do that, how would I later authenticate against the token?
You are correct -- You will add an endpoint which issues JWT tokens once authenticated, and then annotate your other protected resources to use JWT authentication.
Check out dropwizard-auth-jwt, which adds JWT support to Dropwizard. There is an example on how to use it in their examples directory on Github.
Specifically look at the SecuredResource class, which can both issue a token as well as validate it.
You can for instance just extend your aliveSecure method to issue a JWT token.
I made an example project available at on Github which uses basic auth to issue tokens, and #Roles with JWTs.

OAuth2 - handle password change in Spring Security

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?

Check extra parameters with Spring Security

Please give a hint in Spring Security, how can I check additional parameters during user login.
For example, to check not only "username" and "password", but also if he confirmed his registration with email link;
All data is stored in DB and i can get it easily with my implementation of UserDetailsService;
But how to make the security service to pay attention to the additional parameter "isValidated"?
I'm using Spring 3.2.0 now;
One way to achieve this is to create a custom AuthenticationProvider or to extend an existing one. In your case it might be sufficient to extend for instance the DaoAuthenticationProvider and put the logic for checking whether the account is confirmed in additionalAuthenticationChecks() method.
Here is an example:
public class CustomAuthenticationProvider extends DaoAuthenticationProvider {
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
// Perform the checks from the super class
super.additionalAuthenticationChecks(userDetails, authentication);
// Cast the UserDetails to the implementation you use
User user = (User) userDetails;
// Check the confirmed status
if (!user.isAccountConfirmed()) {
throw new AccountNotConfirmedException("Account is not confirmed yet.");
}
}
public static class AccountNotConfirmedException extends AuthenticationException {
public AccountNotConfirmedException(String message) {
super(message);
}
}
}
Your implementation of UserDetails should contain the information about account confirmation status. You can map this information in your implementation of UserDetailsService.
Option 2
Edit: Now that I look at it, the first solution is a bit overcomplicated. You can easily solve this problem without using custom AuthenticationProvider. Just make sure that isEnabled() of your UserDetails implementation returns false if the account is not confirmed. If the enabled property is false authentication will not be allowed (this is automatically taken care of by Spring Security).
The first solution might still be useful if you want explicitly handle the AccountNotConfirmedException in AuthenticationFailureHandler for instance.

Categories

Resources