Spring Boot Basic Authentication without Session (Stateless Session) - java

I have configured Basic Authentication my Spring-Boot application. Everything is Java Config, no xml.
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Authenticate username -> admin, password -> admin & set role as "ROLE_USER"
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
// All Requests should be Authenticated
.anyRequest().authenticated()
.and()
// Enable Basic Authentication
.httpBasic()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/main", true)
.loginProcessingUrl("/session")
.usernameParameter("Username").passwordParameter("Password")
.and()
.logout().logoutUrl("/logout").permitAll()
.and().csrf().disable();
}
}
It's configured for both Basic authentication and normal form login. When I tested the basic authentication from Rest-Client on Firefox, I can access the secure url "/main". But in the response headers, I'm getting Set-Cookie: JSESSIONID=301225C7AE7C74B0892887389996785D;.
I don't want cookies to be generated for basic authentication. I want true Stateless session for Basic Authentication. Do note that I need cookies to be generated for form-login to work, so disabling cookies is not an option. I know about the create-session="stateless" in xml configuration, but is there any way to do the same in Java config so that Basic Authentication is Stateless and Form-Authentication is Statefull..?

I know about the create-session="stateless" in xml configuration, but is there any way to do the same in Java config so that Basic Authentication is Stateless and Form-Authentication is Statefull..?
You can do the following.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
And For your problem following custom Java Config can be used.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
#Inject
UserDetailsService userService;
#Bean
public AuthenticationManager authenticationManager() throws Exception {
AuthenticationManager authenticationManager = new ProviderManager(
Arrays.asList(authenticationProvider()));
return authenticationManager;
}
#Bean
public AuthenticationProvider authenticationProvider() throws Exception {
CustomAuthenticationProvider authenticationProvider = new CustomAuthenticationProvider();
authenticationProvider.setUserDetailsService(userService);
authenticationProvider.setSaltSource(saltSource());
authenticationProvider.setPasswordEncoder(passwordEncoder());
authenticationProvider.afterPropertiesSet();
return authenticationProvider;
}
#Bean
public SaltSource saltSource() throws Exception {
ReflectionSaltSource saltSource = new ReflectionSaltSource();
saltSource.setUserPropertyToUse("salt");
saltSource.afterPropertiesSet();
return saltSource;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new Md5PasswordEncoder();
}
#Bean
public FilterChainProxy springSecurityFilterChain()
throws ServletException, Exception {
List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>();
securityFilterChains.add(new DefaultSecurityFilterChain(
new AntPathRequestMatcher("/login**")));
securityFilterChains.add(new DefaultSecurityFilterChain(
new AntPathRequestMatcher("/resources/**")));
securityFilterChains.add(new DefaultSecurityFilterChain(
new AntPathRequestMatcher("/api/**"),
securityContextPersistenceFilterASCFalse(),
basicAuthenticationFilter(), exceptionTranslationFilter(),
filterSecurityInterceptor()));
securityFilterChains.add(new DefaultSecurityFilterChain(
new AntPathRequestMatcher("/**"),
securityContextPersistenceFilterASCTrue(), logoutFilter(),
usernamePasswordAuthenticationFilter(),
exceptionTranslationFilter(), filterSecurityInterceptor()));
return new FilterChainProxy(securityFilterChains);
}
#Bean
public SecurityContextPersistenceFilter securityContextPersistenceFilterASCTrue() {
return new SecurityContextPersistenceFilter(
new HttpSessionSecurityContextRepository());
}
#Bean
public SecurityContextPersistenceFilter securityContextPersistenceFilterASCFalse() {
HttpSessionSecurityContextRepository httpSessionSecurityContextRepository = new HttpSessionSecurityContextRepository();
httpSessionSecurityContextRepository.setAllowSessionCreation(false);
return new SecurityContextPersistenceFilter(
httpSessionSecurityContextRepository);
}
#Bean
public ExceptionTranslationFilter exceptionTranslationFilter() {
ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(
new LoginUrlAuthenticationEntryPoint("/login"));
AccessDeniedHandlerImpl accessDeniedHandlerImpl = new AccessDeniedHandlerImpl();
accessDeniedHandlerImpl.setErrorPage("/exception");
exceptionTranslationFilter
.setAccessDeniedHandler(accessDeniedHandlerImpl);
exceptionTranslationFilter.afterPropertiesSet();
return exceptionTranslationFilter;
}
#Bean
public UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter()
throws Exception {
UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter = new UsernamePasswordAuthenticationFilter();
usernamePasswordAuthenticationFilter
.setAuthenticationManager(authenticationManager());
usernamePasswordAuthenticationFilter.setAllowSessionCreation(true);
SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler(
"/index");
successHandler.setAlwaysUseDefaultTargetUrl(true);
usernamePasswordAuthenticationFilter
.setAuthenticationSuccessHandler(successHandler);
usernamePasswordAuthenticationFilter
.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(
"/login?error=true"));
usernamePasswordAuthenticationFilter
.setAuthenticationDetailsSource(new CustomWebAuthenticationDetailsSource());
usernamePasswordAuthenticationFilter.afterPropertiesSet();
return usernamePasswordAuthenticationFilter;
}
#Bean
public FilterSecurityInterceptor filterSecurityInterceptor()
throws Exception {
FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
filterSecurityInterceptor
.setAuthenticationManager(authenticationManager());
filterSecurityInterceptor
.setAccessDecisionManager(accessDecisionManager());
filterSecurityInterceptor.setRunAsManager(runAsManager());
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>();
List<ConfigAttribute> configs = new ArrayList<ConfigAttribute>();
configs.add(new org.springframework.security.access.SecurityConfig(
"isAuthenticated()"));
requestMap.put(new AntPathRequestMatcher("/**"), configs);
FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource = new ExpressionBasedFilterInvocationSecurityMetadataSource(
requestMap, new DefaultWebSecurityExpressionHandler());
filterSecurityInterceptor
.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
filterSecurityInterceptor.afterPropertiesSet();
return filterSecurityInterceptor;
}
public AffirmativeBased accessDecisionManager() throws Exception {
List<AccessDecisionVoter> voters = new ArrayList<AccessDecisionVoter>();
voters.add(new WebExpressionVoter());
voters.add(new RoleVoter());
AffirmativeBased affirmativeBased = new AffirmativeBased(voters);
affirmativeBased.setAllowIfAllAbstainDecisions(false);
affirmativeBased.afterPropertiesSet();
return affirmativeBased;
}
#Bean
public RunAsManager runAsManager() throws Exception {
RunAsManagerImpl runAsManager = new RunAsManagerImpl();
runAsManager.setKey("V_RUN_AS");
runAsManager.afterPropertiesSet();
return runAsManager;
}
#Bean
public LogoutFilter logoutFilter() throws ServletException {
List<LogoutHandler> handlers = new ArrayList<LogoutHandler>();
handlers.add(new CookieClearingLogoutHandler("JSESSIONID"));
handlers.add(new SecurityContextLogoutHandler());
LogoutFilter logoutFilter = new LogoutFilter("/login",
handlers.toArray(new LogoutHandler[] {}));
logoutFilter.afterPropertiesSet();
return logoutFilter;
}
#Bean
public RequestContextFilter requestContextFilter() {
return new RequestContextFilter();
}
#Bean
public BasicAuthenticationFilter basicAuthenticationFilter()
throws Exception {
BasicAuthenticationEntryPoint basicAuthenticationEntryPoint = new BasicAuthenticationEntryPoint();
basicAuthenticationEntryPoint.setRealmName("V_REALM");
basicAuthenticationEntryPoint.afterPropertiesSet();
BasicAuthenticationFilter basicAuthenticationFilter = new BasicAuthenticationFilter(
authenticationManager(), basicAuthenticationEntryPoint);
basicAuthenticationFilter
.setAuthenticationDetailsSource(new CustomWebAuthenticationDetailsSource());
basicAuthenticationFilter.afterPropertiesSet();
return basicAuthenticationFilter;
}
}
This configuration creates two different authentication mechanism.
For any request starting with /api/* it will be using a basicAuthenticationFilter and securityContextPersistenceFilterASCFalse with Session Creation False.
For any request starting with /* it will be using a usernamePasswordAuthenticationFilter and securityContextPersistenceFilterASCTrue with Session Creation True.
You can make use of this and alter it to cater your problem.

For anyone else that comes across this, here's something else to check.
I was hitting this same problem with Spring Boot and even with
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
I was still seeing JSESSIONID cookies being set. In my case (using JWT), the missing piece seemed to be setting setAllowSessionCreation on the HttpSessionSecurityContextRepository object, like this:
public class StatelessAuthenticationFilter extends GenericFilterBean {
private final MyTokenAuthService authenticationService;
private SecurityContextRepository repository = new HttpSessionSecurityContextRepository();
protected final Logger logger = LoggerFactory.getLogger(getClass().getName());
public StatelessAuthenticationFilter(MyTokenAuthService authenticationService) {
this.authenticationService = authenticationService;
((HttpSessionSecurityContextRepository) repository).setAllowSessionCreation(false);
}
}
What pointed me this were these lines in HttpSessionSecurityContextRepository:
private boolean allowSessionCreation = true;

Related

How to get Keycloak token in Spring

I have Spring application with Angular frontend, I secured the two sides with Keycloak 11.0.2, how can I get the token information when a request is sent from a frontend, for example I want to access the authenticated user information and attributes in Spring side, because I the returned request depends on the users attributes.
Below is my configuration in spring :
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.realm=myapp
keycloak.resource=myapp-api
keycloak.ssl-required=external
keycloak.bearer-only=true
keycloak.principal-attribute=preferred_username
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(jsr250Enabled = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests().anyRequest().permitAll();
http.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
Based upon your question, I think you are referring to the ID token, which you can get (for instance) from the request. Check the following example:
#GetMapping(path = "/student")
public String teen(HttpServletRequest request) throws ServletException {
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) request.getUserPrincipal();
System.out.println("---- ROLES ----");
token.getAccount().getRoles().forEach(System.out::println);
Map<String, Object> otherClaims = token.getAccount().getKeycloakSecurityContext().getIdToken().getOtherClaims();
Enumeration<String> attributeNames = request.getAttributeNames();
while (attributeNames.hasMoreElements())
System.out.println(attributeNames.nextElement());
for(String s : otherClaims.keySet()){
System.out.println(s);
System.out.println(otherClaims.get(s).toString());
}
System.out.println("------------");
return "student";
}
The code above is just an example, but is showcases some on the API functionality.

Spring security with java config not protecting urls

Several days ago I migrated from xml config to Java config and suddenly got that Spring security does not protecting any url, filters not working at all!
I implemented WebApplicationInitializer interface to configure Dispatcher servlet, and my config looks this:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation("com.test.project");
servletContext.addListener(new ContextLoaderListener(context));
ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher",
new DispatcherServlet(context));
servlet.setLoadOnStartup(1);
servlet.addMapping("/rest/*");
}
Security config looks like this:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
public AutheProvider authProvider() {
return new AuthProvider();
}
#Bean
public AuthenticationFilter authenticationFilter() {
return new AuthenticationFilter();
}
#Bean
public UnauthorizedEntryPoint unauthorizedEntryPoint() {
return new UnauthorizedEntryPoint();
}
#Bean
#Override
public AuthenticationManager authenticationManager() {
ProviderManager providerManager = new ProviderManager(
Arrays.asList(authProvider()));
providerManager.setEraseCredentialsAfterAuthentication(false);
return providerManager;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint())
.and()
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/rest/**").authenticated();
}
}
What I have missed?
SecurityConfiguration class is located in com.test.project package.
After project starts I can access any URL, there is no authenticationobject in SecurityContext and filters are never called, neiter mine, nor Spring Security filters.

Spring Boot Rest API with JWT authentication

I have a Spring Boot Rest API which I will authenticate against and return a JWT token for further API authorization.
My problem is, that the POST request to my auth endpoint doesn't return the JWT Token, therefore I think my problem is inside the authentication workflow. More specific: it never reaches the successfulAuthentication method of the JWTAuthenticationFilter.
A test user is created on startup, credentials are correct (checked the pass encoding).
I have the following classes:
Rest API \
├── JWTAuthenticationFilter
├── SecurityConfiguration
├── UserDetailsServiceImpl
My SecurityConfig where I add the filters and enable the BCrypt password encoder:
#EnableWebSecurity
#EnableJpaRepositories(basePackageClasses = AccountRepo.class)
#Configuration
#EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled=true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
// add filters
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(authenticationUrl).permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable();
}
#Bean(name="passwordEncoder")
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
JWTAuthenticationFilter:
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
try {
AuthenticationRequest creds = new ObjectMapper()
.readValue(req.getInputStream(), AuthenticationRequest.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getEmail(),
creds.getPassword(),
new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
String token = Jwts.builder()
.setSubject(((User) auth.getPrincipal()).getUsername())
.setExpiration(Date.from(OffsetDateTime.now().plusMinutes(EXPIRATION_TIME).toInstant()))
.signWith(SignatureAlgorithm.HS512, SECRET.getBytes())
.compact();
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
}
}
UserDetailsServiceImpl, where I look up the user return the user with authorities:
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private AccountService service;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
VAccount account = service.getAccountByEmail(email);
if(account == null) {
throw new UsernameNotFoundException("no user found with email '" + email + "'");
}
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
for (VRole role : account.getRoles()){
System.out.println(role.getName().toString());
grantedAuthorities.add(new SimpleGrantedAuthority(role.getName().toString()));
}
return new org.springframework.security.core.userdetails.User(account.getEmail(), account.getPassHash(), grantedAuthorities);
}
}
Does anyone see any problems with that auth flow or where it might break?

Spring Boot + Oauth2 client credentials

I am trying to protect my microservices on Spring Boot using Oath2 with Client Credentials flow.
By the way, those microservices will only talk each other over the middleware layer, I mean no user credentials are needed to allow the authorization (user login process as Facebook).
I have looked for samples on the Internet showing how to create an authorization and resource server to manage this communication. However I just found examples explaining how to do it using user credentials (three legs).
Does anyone have any sample how to do it in Spring Boot and Oauth2? If it is possible give further details about the scopes used, token exchanging would be grateful.
We have REST services protected with Oauth2 Client credentials scheme. The Resource and authorization service are running in the same app, but can be split into different apps.
#Configuration
public class SecurityConfig {
#Configuration
#EnableResourceServer
protected static class ResourceServer extends ResourceServerConfigurerAdapter {
// Identifies this resource server. Usefull if the AuthorisationServer authorises multiple Resource servers
private static final String RESOURCE_ID = "*****";
#Resource(name = "OAuth")
#Autowired
DataSource dataSource;
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests().anyRequest().authenticated();
// #formatter:on
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);
resources.tokenStore(tokenStore());
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Resource(name = "OAuth")
#Autowired
DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore());
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
}
}
Datasource config for the Oauth2 tables:
#Bean(name = "OAuth")
#ConfigurationProperties(prefix="datasource.oauth")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
Communicating with authentication & resource server goes as followed
curl -H "Accept: application/json" user:password#localhost:8080/oauth/token -d grant_type=client_credentials
curl -H "Authorization: Bearer token" localhost:8080/...
The following record is present in the Oauth2 Database:
client_id resource_ids client_secret scope authorized_grant_types web_server_redirect_uri authorities access_token_validity refresh_token_validity additional_information autoapprove
user **** password NULL client_credentials NULL X NULL NULL NULL NULL
Resttemplate configuration in client application
#Configuration
#EnableOAuth2Client
public class OAuthConfig {
#Value("${OAuth2ClientId}")
private String oAuth2ClientId;
#Value("${OAuth2ClientSecret}")
private String oAuth2ClientSecret;
#Value("${Oauth2AccesTokenUri}")
private String accessTokenUri;
#Bean
public RestTemplate oAuthRestTemplate() {
ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setId("1");
resourceDetails.setClientId(oAuth2ClientId);
resourceDetails.setClientSecret(oAuth2ClientSecret);
resourceDetails.setAccessTokenUri(accessTokenUri);
/*
When using #EnableOAuth2Client spring creates a OAuth2ClientContext for us:
"The OAuth2ClientContext is placed (for you) in session scope to keep the state for different users separate.
Without that you would have to manage the equivalent data structure yourself on the server,
mapping incoming requests to users, and associating each user with a separate instance of the OAuth2ClientContext."
(http://projects.spring.io/spring-security-oauth/docs/oauth2.html#client-configuration)
Internally the SessionScope works with a threadlocal to store variables, hence a new thread cannot access those.
Therefore we can not use #Async
Solution: create a new OAuth2ClientContext that has no scope.
*Note: this is only safe when using client_credentials as OAuth grant type!
*/
// OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, oauth2ClientContext);
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, new DefaultOAuth2ClientContext());
return restTemplate;
}
}
You can inject the restTemplate to talk (Asynchronously) to the Oauth2 secured service.
We do not use scope at the moment.
Update with Spring-boot-2.7 and Java 17.
https://chuangtc.com/Java/spring-boot-27-security-social-login.php
public class SecurityConfig {
#Value("${spring.social.facebook.appSecret}")
String appSecret;
#Value("${spring.social.facebook.appId}")
String appId;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private FacebookConnectionSignup facebookConnectionSignup;
#Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userDetailsService)
.and()
.build();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/login*", "/signin/**", "/signup/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout();
return http.build();
}
#Bean
// #Primary
public ProviderSignInController providerSignInController() {
ConnectionFactoryLocator connectionFactoryLocator = connectionFactoryLocator();
UsersConnectionRepository usersConnectionRepository = getUsersConnectionRepository(connectionFactoryLocator);
((InMemoryUsersConnectionRepository) usersConnectionRepository).setConnectionSignUp(facebookConnectionSignup);
return new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository, new FacebookSignInAdapter());
}
private ConnectionFactoryLocator connectionFactoryLocator() {
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(appId, appSecret));
return registry;
}
private UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new InMemoryUsersConnectionRepository(connectionFactoryLocator);
}
}
FacebookSignInAdapter
#Service
public class FacebookSignInAdapter implements SignInAdapter {
#Override
public String signIn(String localUserId, Connection<?> connection, NativeWebRequest request) {
System.out.println(" ====== Sign In adapter");
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(connection.getDisplayName(), null, Arrays.asList(new SimpleGrantedAuthority("FACEBOOK_USER"))));
return null;
}
}

Spring boot Security Config - authenticationManager must be specified

Here's my main Application config
#SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.banner((environment, aClass, printStream) ->
System.out.println(stringBanner()))
.run();
}
}
And here's my spring security application config.
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private WebServiceAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private TokenProcessingFilter authTokenProcessingFilter;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // Restful hence stateless
.and()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler) // Notice the entry point
.and()
.addFilter(authTokenProcessingFilter) // Notice the filter
.authorizeRequests()
.antMatchers("/resources/**", "/api/auth")
.permitAll()
.antMatchers("/greeting")
.hasRole("USER");
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user")
.password("password")
.roles("USER");
}
}
Here's my TokenProcessingFilter that extends UsernamePasswordAuthenticationFilter for my custom authentication filter
#Component
public class TokenProcessingFilter extends UsernamePasswordAuthenticationFilter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = this.getAsHttpRequest(request);
String authToken = this.extractAuthTokenFromRequest(httpRequest);
String userName = TokenUtils.getUserNameFromToken(authToken);
if (userName != null) {/*
UserDetails userDetails = userDetailsService.loadUserByUsername(userName);*/
UserDetails userDetails = fakeUserDetails();
if (TokenUtils.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}
chain.doFilter(request, response);
}
private HttpServletRequest getAsHttpRequest(ServletRequest request){
if (!(request instanceof HttpServletRequest)) {
throw new RuntimeException("Expecting an HTTP request");
}
return (HttpServletRequest) request;
}
private String extractAuthTokenFromRequest(HttpServletRequest httpRequest) {
/* Get token from header */
String authToken = httpRequest.getHeader("x-auth-token");
/* If token not found get it from request parameter */
if (authToken == null) {
authToken = httpRequest.getParameter("token");
}
return authToken;
}
private UserDetails fakeUserDetails(){
UsernamePasswordAuthenticationToken authenticationToken = new
UsernamePasswordAuthenticationToken("user","password");
List<SimpleGrantedAuthority> auth= new ArrayList<>();
auth.add(new SimpleGrantedAuthority("USER"));
return new User("user","password",auth);
}
}
however when running the application, I encounter this exception message. What am I missing?
An exception occured while running. null: InvocationTargetException:
Unable to start embedded container; nested exception is
org.springframework.boot.context.embedded.EmbeddedServletContainerException:
Unable to start embedded Tomcat: Error creating bean with name
'tokenProcessingFilter' defined in file
[C:\Users\kyel\projects\app\target\classes\org\app\testapp\security\TokenProcessingFilter.class]:
Invocation of init method failed; nested exception is
java.lang.IllegalArgumentException: authenticationManager must be
specified
You need to set the AuthenticationManager on TokenProcessingFilter. Instead of using #Component on TokenProcessingFilter, just create it in the SecurityConfig.
#Bean
TokenProcessingFilter tokenProcessingFilter() {
TokenProcessingFilter tokenProcessingFilter = new TokenProcessingFilter();
tokenProcessingFilter.setAuthenticationManager(authenticationManager());
return tokenProcessingFilter;
}
and
protected void configure(HttpSecurity http) throws Exception {
...
.addFilter(tokenProcessingFilter())
In order to keep Component annotation you have to override setAuthenticationManager from AbstractAuthenticationProcessingFilter and autowire the parameter like this:
#Component
public class TokenProcessingFilter extends UsernamePasswordAuthenticationFilter {
//...
#Override
#Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
//...
}

Categories

Resources