I'm using spring basic authentication with a custom authentication provider:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider authProvider;
#Override
protected void configure(
AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
}
And
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
if (customauth()) { // use the credentials
// and authenticate against the third-party system
{
return new UsernamePasswordAuthenticationToken(
name, password, new ArrayList<>());
}
} else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(
UsernamePasswordAuthenticationToken.class
);
}
To test this I'm using postman with the following tests:
invalid credentials -> 401 unauthorized
correct credentials -> 200 OK
invalid credentials -> 200 OK
My problem is that the last request should return 401 unauthorized and every following request after a successful login is 200 OK even with a wrong token and without token.
Thanks in advance.
When you logged in successfully, Spring Security will create an Authentication object and will put it in SecurityContext in your HTTP session. As far as you have a valid session with a valid Authentication object at the server, Spring Security won't authenticate your request again and will use the Authentication object saved in your session.
This is a Spring Security feature, see SEC-53:
Check the SecurityContextHolder for an authenticated Authentication and reuse it in that case, do not call the authentication manager again.
If you like to reauthenticate, you could
use no session at all
logout before reauthenticate
In both cases Spring Security will not find an authenticated user saved in the session and will use the new username and password for authentication.
Related
I want to get authorization with bearer token , i generate my access token using credentials from keycloak ,the problem is when i use this access token in the authorization with bearer i got always unauthorized. and when i use basic auth with credentials (username and password) i can access to the ressources wanted,still have this problem why i cannot access with bearer token
this my configuration
keycloak.realm = SpringBootKeycloak
keycloak.auth-server-url = http://127.0.0.1:8080
keycloak.ssl-required = external
keycloak.resource =springboot-microservice
keycloak.enable-basic-auth =true
keycloak.credentials.secret =2bbkqjIYxtB2TOdiXSfO0mJYwXoFoLN7
keycloak.bearer-only=true
keycloak-public-key=0OaMHHI4BR0bZAEr0zK7rByP8ckYKVOItjh8sGQzd7AkSbZhXARF02gpgCHuqZHO14EhycaAazC5GoOnea7ZFpQTs6mNu11mCnCiFBrpkh9MPUhIyUAdg0ohm8j39xdE0TTydLFeJLBs7E6vH1MPFqTyut7DnIuVXFu7Qwi3tDoDdNOBhdNAU9Q3z-CXxTdlVgEv8ICpt-UsfKIt7bgNtpPKdO_ozq_tZcosXCZ-ZS7AFbav2O0WM00tlBhmjq2hZXqzI55msOkzu9T1lp9cQRGcCZF5T9yhHWP_n1LmOaB-M-n3TAHGWFki9T83ecVVi2L5HRPNRRBPbKh23xjiWQ
#KeycloakConfiguration
#EnableWebSecurity
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter{
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
/**
* Defines the session authentication strategy.
*/
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(buildSessionRegistry());
}
#Bean
protected SessionRegistry buildSessionRegistry() {
return new SessionRegistryImpl();
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
super.configure(http);
http
.authorizeRequests()
// .anyRequest().authenticated();
.antMatchers("/MSJournalisation/**").hasAuthority("user");
// .anyRequest().authenticated();
}
}
for keycloak i use the latest version 17
I am using spring security with custom Authentication Provider using basic auth.
When I am trying to hit backend API GET call through postman it is working fine only when I make changes in username
Here is the problem statement - whenever I modify the user name then only custom authenticator provider works. once I added the correct username and password then it works but after that when I am making any changes in password (giving wrong password) always showing 200 success response. If I am making changes in username (giving wrong username) then only call to custom authenticator provider happened and getting 401 response.
Java Spring code
#Configuration
#EnableWebSecurity
#ComponentScan("com.authentication.service")
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private AuthService authProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception { http
.httpBasic().and().logout().clearAuthentication(true).and() .authorizeRequests()
.antMatchers("/index.html", "/", "/home", "/login", "/assets/**").permitAll()
.anyRequest().authenticated() .and() .csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); }
}
#Service
public class AuthService implements AuthenticationProvider{
#Override
public Authentication authenticate(Authentication authentication)
throws org.springframework.security.core.AuthenticationException {
String userName = (String) authentication.getPrincipal();
String userPassword = authentication.getCredentials().toString();
if(authenticateUser(userName, userPassword)) {
return new UsernamePasswordAuthenticationToken(userName, userPassword, new ArrayList<>());
} else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
public Boolean authenticateUser(String userName, String userPassword) {
// Using some third party service
// return true if user is authenticated else false
}
}
This is occurring because Spring Security is creating a session that is returned as a Cookie upon successful authentication (You can check this in the Cookies panel in the Postman's response). For Further requests, even if you provide invalid credentials, Postman will send this session cookie that will be used for Authentication.
To remove this effect, you can update your session management policy to be SessionCreationPolicy.STATELESS, this will make sure no session is created by the application and the Basic Auth credentials that are sent in the request are used for authentication.
You can update the Session Management Policy like this:
#Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
I am using okta to do authentication. Our company's okta disabled the 'default' authorization server. So right now I cannot use 'okta-spring-security-starter' to simple do this to verify token passed from url headers:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
public class OktaOAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/health").permitAll()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
http.cors();
Okta.configureResourceServer401ResponseBody(http);
}
}
So I need to hit okta introspect endpoint (https://developer.okta.com/docs/reference/api/oidc/#introspect) to verify. So I am wondering can I integrate this procedure within the config of WebSecurityConfigurerAdapter. maybe something like this???:
import com.okta.spring.boot.oauth.Okta;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
public class OktaOAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/health").permitAll()
.anyRequest().authenticated()
.and()
/*add something there*/
http.cors();
}
}
I saw something like override AuthenticationProvider(Custom Authentication provider with Spring Security and Java Config), and use httpbasic auth. Can I do similiar thing if I use .oauth2ResourceServer().jwt().
My idea is override the authentication provider and in the provider, hit the okta introspect endpoint, will this work???
Spring Security 5.2 ships with support for introspection endpoints. Please take a look at the Opaque Token sample in the GitHub repo.
To answer briefly here, though, you can do:
http
.authorizeRequests(authz -> authz
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.opaqueToken(opaque -> opaque
.introspectionUri("the-endpoint")
.introspectionClientCredentials("client-id", "client-password")
)
);
If you are using Spring Boot, then it's a bit simpler. You can provide those properties in your application.yml:
spring:
security:
oauth2:
resourceserver:
opaquetoken:
introspection-uri: ...
client-id: ...
client-secret: ...
And then your DSL can just specify opaqueToken:
http
.authorizeRequests(authz -> authz
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.opaqueToken(opaque -> {})
);
I don't use Okta thus I don't know how exactly it works. But I have 2 assumptions:
Every request contains an accessToken in the Authorization header
You make a POST request to ${baseUrl}/v1/introspect and it will answer you with true or false to indicate that accessToken is valid or not
With these 2 assumptions in mind, if I have to manually implement custom security logic authentication, I would do following steps:
Register and implement a CustomAuthenticationProvider
Add a filter to extract access token from request
Registering custom authentication provider:
// In OktaOAuth2WebSecurityConfig.java
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider());
}
#Bean
CustomAuthenticationProvider customAuthenticationProvider(){
return new CustomAuthenticationProvider();
}
CustomAuthenticationProvider:
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationProvider.class);
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
logger.debug("Authenticating authenticationToken");
OktaTokenAuthenticationToken auth = (OktaTokenAuthenticationToken) authentication;
String accessToken = auth.getToken();
// You should make a POST request to ${oktaBaseUrl}/v1/introspect
// to determine if the access token is good or bad
// I just put a dummy if here
if ("ThanhLoyal".equals(accessToken)){
List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("USER"));
logger.debug("Good access token");
return new UsernamePasswordAuthenticationToken(auth.getPrincipal(), "[ProtectedPassword]", authorities);
}
logger.debug("Bad access token");
return null;
}
#Override
public boolean supports(Class<?> clazz) {
return clazz == OktaTokenAuthenticationToken.class;
}
}
To register the filter to extract accessToken from request:
// Still in OktaOAuth2WebSecurityConfig.java
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(accessTokenExtractorFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests().anyRequest().authenticated();
// And other configurations
}
#Bean
AccessTokenExtractorFilter accessTokenExtractorFilter(){
return new AccessTokenExtractorFilter();
}
And the filter it self:
public class AccessTokenExtractorFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(AccessTokenExtractorFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
logger.debug("Filtering request");
Authentication authentication = getAuthentication(request);
if (authentication == null){
logger.debug("Continuing filtering process without an authentication");
filterChain.doFilter(request, response);
} else {
logger.debug("Now set authentication on the request");
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}
private Authentication getAuthentication(HttpServletRequest request) {
String accessToken = request.getHeader("Authorization");
if (accessToken != null){
logger.debug("An access token found in request header");
List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("USER"));
return new OktaTokenAuthenticationToken(accessToken, authorities);
}
logger.debug("No access token found in request header");
return null;
}
}
I have uploaded a simple project here for your easy reference: https://github.com/MrLoyal/spring-security-custom-authentication
How it works:
The AccessTokenExtractorFilter is placed right after the UsernamePasswordAuthenticationFilter, which is a default filter by Spring Security
A request arrives, the above filter extracts accessToken from it and place it in the SecurityContext
Later, the AuthenticationManager calls the AuthenticationProvider(s) to authenticate request. This case, the CustomAuthenticationProvider is invoked
BTW, your question should contain spring-security tag.
Update 1: About AuthenticationEntryPoint
An AuthenticationEntryPoint declares what to do when an unauthenticated request arrives ( in our case, what to do when the request does not contain a valid "Authorization" header).
In my REST API, I simply response 401 HTTP status code to client.
// CustomAuthenticationEntryPoint
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.reset();
response.setStatus(401);
// A utility method to add CORS headers to the response
SecUtil.writeCorsHeaders(request, response);
}
Spring's LoginUrlAuthenticationEntryPoint redirects user to login page if one is configured.
So if you want to redirect unauthenticated requests to Okta's login page, you may use a AuthenticationEntryPoint.
I have two different types of users.
SSO users
DB users.
SSO users would have already been authenticated by different system and DB users should be authenticated by our system. Can i configure Spring security to handle this scenario where by i can say prompt login page for some users and don't prompt for some.
Lets say for SSO users i can get there users ID in request headers while DB when they access the application there is no user id present in request header .How can i handle this scenario ?
Can i override authenticate method of DaoAuthenticationProvider by extending it and then page on some parameter decide to authenticate user or is there any other means ? can i add any information to Authentication class
This is What i have tried to Far
Security Config java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder builder) throws Exception {
builder.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder())
.usersByUsernameQuery("select username,password, enabled from users where username=?")
.authoritiesByUsernameQuery("select username, role from user_roles where username=?");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().httpBasic()
.and().addFilterBefore(new UserTypeFilter(), BasicAuthenticationFilter.class);
}
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
/*
* #Bean public MethodSecurityInterceptor methodSecurityService() { return
* new MethodSecurityInterceptor(); }
*/
#Bean(name="myAuthenticationManager")
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public ExceptionTranslationFilter exceptionTranslationFilter() {
ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(
new Http403ForbiddenEntryPoint());
AccessDeniedHandlerImpl accessDeniedHandlerImpl = new AccessDeniedHandlerImpl();
accessDeniedHandlerImpl.setErrorPage("/exception");
exceptionTranslationFilter
.setAccessDeniedHandler(accessDeniedHandlerImpl);
exceptionTranslationFilter.afterPropertiesSet();
return exceptionTranslationFilter;
}
#Bean
public UserTypeFilter authenticationFilter() throws Exception {
UserTypeFilter authFilter = new UserTypeFilter();
authFilter.setAuthenticationManager(authenticationManager());
return authFilter;
}
}
My Custom AbstractAuthenticationProcessingFilter
public class UserTypeFilter extends AbstractAuthenticationProcessingFilter {
private static final String INTERCEPTOR_PROCESS_URL = "/index";
#Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
public UserTypeFilter() {
super(INTERCEPTOR_PROCESS_URL);
}
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
System.out.println(" BASIC AUTHENTICATION FILTER");
String userId = request.getHeader("USERID");
if (userId == null) {
System.out.println(" THROWING EXCEPTION FILTER");
throw new PreAuthenticatedCredentialsNotFoundException("USERID param not found");
}
return null;
}
}
My Controller
#Controller
public class MainController {
#RequestMapping(value = { "/index" }, method = RequestMethod.GET)
public ModelAndView index() {
ModelAndView model = new ModelAndView();
model.addObject("message", "This is test page!");
model.setViewName("dummy");
return model;
}
}
The control goes to My Custom filter and then when the exception is thrown but ExceptionTranslationFilter is not getting called
Have i configured httpsecurity correctly
Have i configured My custom filter correctly
Have i configured ExceptionTranslation Filter
Am i missing anything
This is a pretty standard use case for spring security. You will need to provide an Authentication object into the security context before any security interceptor is encountered.
Typically you would have some kind of filter which extracted SSO parameters from the request, authenticated those parameters against the SSO service, and then create an Authentication object and put it into the security context. The type of filter and configuration of the filter will depend on what SSO technology you are using.
There would often also be a filter (usually an ExceptionTranslationFilter) which will send unauthenticated requests to a login page.
There would also be filters to receive the parameters from the login form and store them in the security context.
Putting it all together I would expect one possible workflow to be:
User logs in with SSO parameters
Request comes in prepopulated with credentials
Some filter extracts those credentials, verifies them, creates an Authentication object, places the object in the security context.
The security interceptor finds the Authentication object in the security context, verifies the user is allowed access to the particular function, and passes the request on.
User logs in without SSO parameters (needs login page)
Request comes in with no credentials
The security interceptor finds no Authentication object and throws an exception.
The ExceptionTranslationFilter turns the exception into a redirect to a login page.
User logs in with filled out login form (e.g. for DB login)
Request comes in with a login form as the entity body
Some filter (e.g. UsernamePasswordAuthenticationFilter) extracts the credentials from the login form and defers to an authentication provider (e.g. your DAO authentication provider) to query the database and verify the user. If verified this filter will create an Authentication object and place it in the security context.
The security interceptor finds the Authentication object in the security context, verifies the user is allowed access to the particular function, and passes the request on.
I'm using Spring 4 with Spring Security, custom GenericFilterBean and AuthenticationProvider implementations. I have mostly secured URLs with the exception of a URL to create new session: /v2/session (e.g. login based on the username and password and returns Auth Token to be used in the subsequent requests that require authentication) configured as follows:
#Configuration
#ComponentScan(basePackages={"com.api.security"})
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ApiAuthenticationProvider apiAuthenticationProvider;
#Autowired
private AuthTokenHeaderAuthenticationFilter authTokenHeaderAuthenticationFilter;
#Autowired
private AuthenticationEntryPoint apiAuthenticationEntryPoint;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(apiAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(authTokenHeaderAuthenticationFilter, BasicAuthenticationFilter.class) // Main auth filter
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests()
.antMatchers(HttpMethod.POST, "/v2/session").permitAll()
.anyRequest().authenticated();
http.exceptionHandling()
.authenticationEntryPoint(apiAuthenticationEntryPoint);
}
}
The authTokenHeaderAuthenticationFilter runs on every request and gets Token from the request header:
/**
* Main Auth Filter. Always sets Security Context if the Auth token Header is not empty
*/
#Component
public class AuthTokenHeaderAuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final String token = ((HttpServletRequest) request).getHeader(RequestHeaders.AUTH_TOKEN_HEADER);
if (StringUtils.isEmpty(token)) {
chain.doFilter(request, response);
return;
}
try {
AuthenticationToken authRequest = new AuthenticationToken(token);
SecurityContextHolder.getContext().setAuthentication(authRequest);
}
} catch (AuthenticationException failed) {
SecurityContextHolder.clearContext();
return;
}
chain.doFilter(request, response); // continue down the chain
}
}
The custom apiAuthenticationProvider will try to authenticate all requests based on the token provided in the header and if authentication is unsuccessful - throws AccessException and client will receive HTTP 401 response:
#Component
public class ApiAuthenticationProvider implements AuthenticationProvider {
#Autowired
private remoteAuthService remoteAuthService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
AuthenticationToken authRequest = (AuthenticationToken) authentication;
String identity = null;
try {
identity = remoteAuthService.getUserIdentityFromToken(authRequest.getToken());
} catch (AccessException e) {
throw new InvalidAuthTokenException("Cannot get user identity from the token", e);
}
return new AuthenticationToken(identity, authRequest.getToken(), getGrantedAuthorites());
}
}
This works perfectly fine for the requests that require authentication. This works fine for the /v2/session request without the Authentication Header in it. However, for the /v2/session request that has an expired Auth Token in the header (or in the cookie - not shown in the code samples; this may happen sometimes if the client didn't clear the headers or continues sending cookies with requests) the security context will be initialized and apiAuthenticationProvider will throw an exception and respond with HTTP 401 to the client.
Since /v2/session has been configured as
http.authorizeRequests()
.antMatchers(HttpMethod.POST, "/v2/session").permitAll()
I would expect Spring Security to determine that before calling ApiAuthenticationProvider.authenticate(). What should be the way for the filter or auth provider to ignore/not throw the exception for the URLs configured as permitAll()?
Spring security filters get triggered before the request authorisation checks are performed. For the authorisation checks to work, it is assumed that the request has been through the filters and the Spring security context has been set (or not, depending on whether authentication credentials have been passed in).
In your filter you have check that continues with the filter chain processing if the token is not there. Unfortunately, if it is, then it will be passed to your provider for authentication, which throws an exception because the token has expired thus you're getting the 401.
Your best bet is to bypass filter execution for the URLs that you consider public. You can either do this in the filter itself or in your configuration class. Add the following method to your SecurityConfig class:
#Override
public void configure(WebSecurity webSecurity) {
webSecurity.ignoring().antMatchers(HttpMethod.POST, "/v2/session");
}
What this will do, is bypass your AuthTokenHeaderAuthenticationFilter completely for POST /v2/sessions URL.