Multiple custom authentication with spring security - spring

I have a spring application which uses a custom Authentication Filter say filter1 to authorize the request, this filter uses an authentication manager for authentication and is applicable for all urls in application.
Now, I want to implement a different Authentication Filter say filter2 which has to authorize special kind of request say with url (/api/). That is the all the request which has the url like (/api/**) has to use filter2.
Below is the code I've tried so far for this purpose.
public class SecurityAppConfig {
#Configuration
#Order(1)
public static class APISecurityConfig extends WebSecurityConfigurerAdapter {
private CustomAuthenticationManager1 manager1 = new CustomAuthenticationManager1();
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.formLogin().disable().csrf().disable().cors().disable().logout().disable();
if (manager1 != null) {
http.addFilterAfter(new Filter1(manager1),
AnonymousAuthenticationFilter.class);
}
}
}
#Configuration
#Order(2)
public static class OtherApiSecurityConfig extends WebSecurityConfigurerAdapter {
private AuthenticationManager2 manager2 = new AuthenticationManager2();
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.formLogin().disable().csrf().disable().cors().disable().logout().disable();
if (manager2 != null) {
http.antMatchers("/api/**").addFilterAfter(new Filter2(manager2),
AnonymousAuthenticationFilter.class);
}
}
}
}
At the time of app start up both the filter are getting registered with their manager but when this ("/api/**") request comes it goes to the first filter for authentication but never goes to the second filter. If I remove the first filter then it works properly but that would override the filters for other api request.
Below is how I've implemented managers and filters
public class Filter1 extends AbstractAuthenticationProcessingFilter {
//implementation omitted for brevity.
}
public class Filter2 extends AbstractAuthenticationProcessingFilter {
//implementation omitted for brevity.
}
public class AuthenticationManager1 implements AuthenticationManager {
//implementation omitted for brevity.
}
public class AuthenticationManager2 implements AuthenticationManager {
//implementation omitted for brevity.
}
Any thoughts on how can I get this working.

I don't think that you need two configs for your case. And I don't see why you need to implement your own authentication manager, even two of them. I guess you should use shared authentication manager instead, implement your own AuthenticationProvider (one for each type of authentication), and implement youe own authentication tokens. Besides that, since you're using AbstractAuthenticationProcessingFilter as a base class for you filters - you can set filterProcessesUrl into it, so your filter knows to which URL's it should be applied. So, in brief:
Authentication Tokens:
public class MyAuth1AuthenticationToken extends AbstractAuthenticationToken {
// Implementation depends on you auth scheme (you can look on
// `UsernamePasswordAuthenticationToken` for example)
}
public class MyAuth2AuthenticationToken extends AbstractAuthenticationToken {
// ...
}
Authentication Providers:
public class MyAuth1AuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// Implementation really depends on you auth scheme (you can look on
// `AbstractUserDetailsAuthenticationProvider` for example)
}
#Override
public boolean supports(Class<?> authentication) {
// By this we're saying that this auth provider is responsible for our MyAuth1 auth request
return (MyAuth1AuthenticationToken.class.isAssignableFrom(authentication));
}
}
public class MyAuth2AuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// ...
}
#Override
public boolean supports(Class<?> authentication) {
return (MyAuth2AuthenticationToken.class.isAssignableFrom(authentication));
}
}
Filters:
public class Auth1Filter extends AbstractAuthenticationProcessingFilter {
public Auth1Filter(AuthenticationManager authManager, String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
setAuthenticationManager(authManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// extract user info here
// ...
// populate auth request with your info
MyAuth1AuthenticationToken authRequest = new MyAuth1AuthenticationToken(...);
// authenticate
return this.getAuthenticationManager().authenticate(authRequest);
}
}
public class Auth2Filter extends AbstractAuthenticationProcessingFilter {
public Auth2Filter(AuthenticationManager authManager, String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
setAuthenticationManager(authManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// extract user info here
// ...
// populate auth request with your info
MyAuth2AuthenticationToken authRequest = new MyAuth1AuthenticationToken(...);
// authenticate
return this.getAuthenticationManager().authenticate(authRequest);
}
}
Security Config:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception
{
// registering our providers
auth
.authenticationProvider(new MyAuth1AuthenticationProvider())
.authenticationProvider(new MyAuth2AuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin().disable()
.csrf().disable()
.cors().disable()
.logout().disable();
AuthenticationManager authManager = http.getSharedObject(AuthenticationManager.class);
http.addFilterAfter(new Auth1Filter(authManager, "/**"), BasicAuthenticationFilter.class);
http.addFilterAfter(new Auth2Filter(authManager, "/api/**"), BasicAuthenticationFilter.class);
}
}
Hope it helps.

Related

KeyCloak Spring Boot - Add custom code on auth success

I am using KeyCloak integration with Spring Boot as in this guide. I have my security config like below:
class KeycloakSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests().antMatchers("/**").authenticated()
.anyRequest().permitAll();
}
}
I want to add some custom code for onAuthenticationSuccess before KeyCloak redirects me to the actual resource. I tried implementing a custom class with AuthenticationSuccessHandler and do formLogin().successHandler(...). This didn't work. How can I get this working??
If you still prefer to use Spring Boot KeyCloak, something like this will work.
public class KeyCloakAuthSuccessHandler extends KeycloakAuthenticationSuccessHandler {
public KeyCloakAuthSuccessHandler(AuthenticationSuccessHandler fallback) {
super(fallback);
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if (authentication.getPrincipal() instanceof KeycloakPrincipal) {
AccessToken token = ((KeycloakPrincipal<?>) authentication.getPrincipal()).getKeycloakSecurityContext().getToken();
}
super.onAuthenticationSuccess(request, response, authentication);
}
}
And in your security config or similar file that extends KeyCloakWebSecurityConfigurerAdapter do the following:
#Bean
#Override
protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(authenticationManagerBean());
filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
#NotNull
#Bean
public KeyCloakAuthSuccessHandler successHandler() {
return new KeyCloakAuthSuccessHandler(new SavedRequestAwareAuthenticationSuccessHandler());
}

AbstractAuthenticationProcessingFilter triggered no matter what SecurityFilterChain it is a part of

I am experimenting with spring security and came across a strange behavior.
My idea is to create a security filter that authenticates requests based on JWT (or JWS) tokens:
public class JWTokenFilter extends AbstractAuthenticationProcessingFilter {
public JWTokenFilter(AuthenticationManager authenticationManager) {
super("/**"); //doesn't have any effect, every request still gets considered by this filter
setAuthenticationManager(authenticationManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String token = request.getHeader("Authorization");
if (!StringUtils.hasText(token)) {
throw new TokenException("Token is empty");
}
var authentication = determineAuthentication(token.replace("Bearer","").trim());
//the AbstractAuthenticationProcessingFilter fills the Security context
return this.getAuthenticationManager().authenticate(authentication);
}
#Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
System.out.println("Asked for "+request.getRequestURI());
return request.getHeader("Authorization") != null;
}
private TokenAuthentication<UserInfo> determineAuthentication(String token) {
var split = token.split("\\.");
if (split.length < 2 || split.length > 3) {
throw new TokenException("Token malformed");
}
if (split.length == 2){
return new JWTAuthentication<>(token);
}else {
return new JWSAuthentication<>(token);
}
}
}
I have 3 #RestController classes which have their paths mapped:
#RequestMapping("/admin")
#RequestMapping("/all")
#RequestMapping("/anon")
Along with this, I have the following security configuration:
#Configuration
#Order(98)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/all/**","/anon/**")
.and()
.authorizeRequests().antMatchers("/all/**").permitAll()
.and()
.authorizeRequests().antMatchers("/anon/**").anonymous();
}
#Override
public void configure(WebSecurity web) {
web.ignoring().mvcMatchers("/webjars/**", "/css/**");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Configuration
#Order(99)
public static class TokenSecurityConfig extends WebSecurityConfigurerAdapter{
#Lazy
#Autowired
private JWTokenFilter tokenFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //having /admin/** or /** makes no difference
.anyRequest().authenticated()
.and().addFilterBefore(tokenFilter,ExceptionTranslationFilter.class);//put this filter near the end of the chain
}
#Bean
public JWTokenFilter tokenFilter(JWTokenAuthenticationProvider jwTokenAuthenticationProvider,JWSTokenAuthenticationProvider jwsTokenAuthenticationProvider){
var list = new ArrayList<AuthenticationProvider>();
list.add(jwsTokenAuthenticationProvider);
list.add(jwTokenAuthenticationProvider);
ProviderManager manager = new ProviderManager(list);
return new JWTokenFilter(manager);
}
}
}
From this configuration here we can see that there are 2 SecurityFilterChans (not counting the /webjars and /css ones):
That matches all requests for "/all/**" and "/anon/**" REST routes
That matches any request
Since the 1. chain has lower #Order(98), than 2. #Order(99), that means that the 1. chain will be considered first which is shown by the debugger,
and matched if the incoming request looks like:
curl --request GET \
--url http://localhost:8080/all/hello \
Now what I am experiencing is that the JWTokenFilter method boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) is always called no matter the request path !
And in the console output, I can find Asked for /all/hello.
Edit:
My spring boot version is 2.3.6.RELEASE
My question is:
Why is the JWTokenFIlter even asked if it should authenticate requests with paths that are not matched by the SecurityFilterChain it is a part of?
I believe I have a better answer, but I wanted to answer your original question as well. I split this into two sections.
Improved Answer
I realize this doesn't answer the original question, but I think you may be better off using the built in support for JWT based authentication. I'd check out the OAuth 2.0 Resource Server section of the reference documentation.
Answer to Original Question
Spring Boot will automatically register any Filter exposed as a #Bean for every request directly with the Servlet Container.
You have two options that I see. The first is to avoid exposing the JwtTokenFilter as a #Bean.
#Configuration
#Order(99)
public static class TokenSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
JWTokenAuthenticationProvider jwTokenAuthenticationProvider;
#Autowired
JWSTokenAuthenticationProvider jwsTokenAuthenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //having /admin/** or /** makes no difference
.anyRequest().authenticated()
.and().addFilterBefore(tokenFilter(),ExceptionTranslationFilter.class);//put this filter near the end of the chain
}
public JWTokenFilter tokenFilter(){
var list = new ArrayList<AuthenticationProvider>();
list.add(jwsTokenAuthenticationProvider);
list.add(jwTokenAuthenticationProvider);
ProviderManager manager = new ProviderManager(list);
return new JWTokenFilter(manager);
}
}
Alternatively, you can continue exposing JwtTokenFilter as a #Bean and create a FilterRegistrationBean that disables registration.
#Bean
public FilterRegistrationBean registration(JWTokenFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}

Unable to use permitAll() with Spring Boot 2.3.4 to allow access to Swagger UI after integrating with API-Key Authentication

I tried integrating API-Key authentication mechanism to Spring Boot Application in the following way:
Created a CustomAPIKeyAuthFilter that extends AbstractPreAuthenticatedProcessingFilter where it gets the preauthenticated principal from the headers of the request.
public class CustomAPIKeyAuthFilter extends AbstractPreAuthenticatedProcessingFilter {
private String principalRequestHeader;
private String principalAuthKey;
public CustomAPIKeyAuthFilter(String principalRequestHeader, String principalAuthKey) {
this.principalRequestHeader = principalRequestHeader;
this.principalAuthKey = principalAuthKey;
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
return request.getHeader(principalRequestHeader);
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
// anything to be returned here??
return "TBD";
}
}
Created WebSecurityConfig that extends WebSecurityConfigurerAdapter. In this one, the custom filter is injected inside the overridden method protected void configure(HttpSecurity httpSecurity) {}
#EnableWebSecurity
#Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${superuser}")
private String principalRequestHeader;
#Value("${superuserauthkey}")
private String principalRequestValue;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
CustomAPIKeyAuthFilter filter = new CustomAPIKeyAuthFilter(principalRequestHeader, principalRequestValue);
filter.setAuthenticationManager(new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String principal = (String) authentication.getPrincipal();
if (principalRequestValue.equals(principal)){
authentication.setAuthenticated(true);
} else {
throw new BadCredentialsException("Missing API Key");
}
return authentication;
}
});
httpSecurity.
cors().and().
csrf().disable().authorizeRequests()
.antMatchers("**swagger**").permitAll() // this is the part that is not working for me
.anyRequest().authenticated()
.and()
.addFilter(filter)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
As you can see from the comment above, even though I used permitAll, I get the error 401 No pre-authenticated principal found in request at runtime if I try to access Swagger UI which was working before introducing spring-boot-starter-security related dependencies in my pom.xml. Is there a better way to exclude swagger UI alone from the list of URL end points that need API-key based authentication ?
Note: I am using springfox-swagger2 implementation of Swagger and the version used is 2.8.0.
Swagger have api endpoint which should be allowed in security level, add the below snippet in WebSecurityConfig.class
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**");
}
You could also try permitAll() to the patterns included.This will exclude the swagger from being authenticated.

Implement Spring Security for Rest Api

I use this code for Rest API authentication:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
Optional<String> basicToken = Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION))
.filter(v -> v.startsWith("Basic"))
.map(v -> v.split("\\s+")).filter(a -> a.length == 2).map(a -> a[1]);
if (!basicToken.isPresent()) {
return sendAuthError(response);
}
byte[] bytes = Base64Utils.decodeFromString(basicToken.get());
String namePassword = new String(bytes, StandardCharsets.UTF_8);
int i = namePassword.indexOf(':');
if (i < 0) {
return sendAuthError(response);
}
String name = namePassword.substring(0, i);
String password = namePassword.substring(i + 1);
// Optional<String> clientId = authenticationService.authenticate(name, password, request.getRemoteAddr());
Merchants merchant = authenticationService.authenticateMerchant(name, password, request.getRemoteAddr());
if (merchant == null) {
return sendAuthError(response);
}
request.setAttribute(CURRENT_CLIENT_ID_ATTRIBUTE, merchant.getId());
return true;
}
How I can rewrite the code with Spring Security in order to get the same result but for different links to have authentication? For example:
localhost:8080/v1/notification - requests should NOT be authenticated.
localhost:8080/v1/request - requests should be authenticated.
Here you can find a working project https://github.com/angeloimm/springbasicauth
I know in the pom.xml file there are a lot of useless dependencies but I started from an already existing project and I had no time to depure it
Basically you must:
configure spring security
configure spring mvc
implements your own authentication provider according to spring security. Note I used an inMemoryAuthentication. Please modify it according to yuor own wishes
Let me explain the code.
Spring MVC Configuration:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages= {"it.olegna.test.basic"})
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter());
}
}
Here we don't do anything else that configuring spring MVC by telling it where to find controllers and so on and to use a single message converter; the MappingJackson2HttpMessageConverter in order to produce JSON responses
Spring Security Configuration:
#Configuration
#EnableWebSecurity
#Import(value= {WebMvcConfig.class})
public class WebSecConfig extends WebSecurityConfigurerAdapter {
#Autowired private RestAuthEntryPoint authenticationEntryPoint;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("test")
.password(passwordEncoder().encode("testpwd"))
.authorities("ROLE_USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/securityNone")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint);
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
Here we configure Spring Security in order to use HTTP Basic Authentication for all requests except the ones starting with securityNone. We use a NoOpPasswordEncoder in order to encode the provided password; this PasswrodEncoder does absolutly nothing... it leaves the passwrod as it is.
RestEntryPoint:
#Component
public class RestAuthEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
This entrypoint disables all requests not containg the Authentication header
SimpleDto: a very simple DTO representing the JSON answer form a controller
public class SimpleDto implements Serializable {
private static final long serialVersionUID = 1616554176392794288L;
private String simpleDtoName;
public SimpleDto() {
super();
}
public SimpleDto(String simpleDtoName) {
super();
this.simpleDtoName = simpleDtoName;
}
public String getSimpleDtoName() {
return simpleDtoName;
}
public void setSimpleDtoName(String simpleDtoName) {
this.simpleDtoName = simpleDtoName;
}
}
TestBasicController: a very simple controller
#RestController
#RequestMapping(value= {"/rest"})
public class TestBasicController {
#RequestMapping(value= {"/simple"}, method= {RequestMethod.GET}, produces= {MediaType.APPLICATION_JSON_UTF8_VALUE})
public ResponseEntity<List<SimpleDto>> getSimpleAnswer()
{
List<SimpleDto> payload = new ArrayList<>();
for(int i= 0; i < 5; i++)
{
payload.add(new SimpleDto(UUID.randomUUID().toString()));
}
return ResponseEntity.ok().body(payload);
}
}
So if you try this project by using postman or any other tester you can have 2 scenarios:
authentication required
all ok
Let's suppose you want to invoke the URL http://localhost:8080/test_basic/rest/simple without passing the Authentication header. The HTTP Status code will be 401 Unauthorized
This means that the Authentication Header is required
By adding this header to the request Authorization Basic dGVzdDp0ZXN0cHdk all works pretty good
Note that the String dGVzdDp0ZXN0cHdk is the Base64 encoding of the string username:password; in our case is the Base64 encoding of test:testpwd defined in the inMemoryAuthentication
I hope this is usefull
Angelo
WEB SECURITY USER DATAIL SERVICE
In order to configure Spring security to retrieve user details from DB you must do the following:
create a org.springframework.security.core.userdetails.UserDetailsService implementation like this:
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private BasicService svc;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
BasicUser result = svc.findByUsername(username);
if( result == null )
{
throw new UsernameNotFoundException("No user found with username "+username);
}
return result;
}
}
Inject it to the spring security configuration and use it like this:
public class WebSecConfig extends WebSecurityConfigurerAdapter {
#Autowired private RestAuthEntryPoint authenticationEntryPoint;
#Autowired
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// auth
// .inMemoryAuthentication()
// .withUser("test")
// .password(passwordEncoder().encode("testpwd"))
// .authorities("ROLE_USER");
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/securityNone")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint);
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
I pushed the code on the github link I provided. There you can find a full working example based on:
spring 5
spring security 5
hibernate
h2 DB
Feel free to adapt it to your own scenario
You can use a default spring-security configuration described on various websites, like baeldung.com or mkyong.com. The trick in your sample seems to be the call to get the Merchant. Depending on the complexity of the authenticationService and the Merchant object, you can either use the following code, or implement a facade to get similar behaviour.
#Autowired
public void authenticationManager(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(new AuthenticationProvider() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Merchants merchant = authenticationService.authenticateMerchant(name, password, request.getRemoteAddr());
if(merchant == null) {
throw new AuthenticationException("No Merchant found.");
}
return new UsernamePasswordAuthenticationToken(name, password, merchant.getAuthorities());
}
#Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
});
}
Setting the attribute on the request, if necessary could be done by a separate filter which takes the Principal from the SecurityContext and puts it on the request as an attribute.

Custom Authentication provider with Spring Security and Java Config

How can I define a custom Authentication provider by using Spring Security with Java Configurations?
I would like to perform a login checking credentials on my own database.
The following does what you need (CustomAuthenticationProvider is your implementation which needs to be managed by Spring)
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
/**
* Do your stuff here
*/
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
}
As shown on baeldung.com, define your authentication provider as follow:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
if (shouldAuthenticateAgainstThirdPartySystem(username, password)) {
// 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);
}
}
and following code is corresponding java config:
#Configuration
#EnableWebSecurity
#ComponentScan("org.project.security")
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();
}
}

Resources