Upgrading the SecurityConfig of my application, I got working version but destroyed so many tests.
Current, deprecated version is here and differecence between files is here
The new version works very well (I tried it with Postman).
With difference version I got every test passed at 100%, now I have several errors that I
cannot solve myself.
E.g. this test:
#WebMvcTest(PingController.class)
class AuthTokenFilterTest {
#Autowired
private MockMvc mvc;
#Autowired
AuthTokenFilter authTokenFilter;
#MockBean
JwtUtils jwtUtils;
#MockBean
UserDetailsServiceImpl userDetailsServiceImpl;
#MockBean
UserDetails userDetails;
#MockBean
private PingService pingService;
#MockBean
AuthEntryPointJwt authEntryPointJwt;
#MockBean
UsersRepository usersRepository;
#Test
void testCanReturnNullIfJwtIsMissing() throws Exception {
mvc.perform(get("/api/v1/ping")).andExpect(status().isOk());
}
/**
* Test can validate the token.
*
* Use /ping route because it is the route out of security, so we can
* concentrate to the AuthTokenFilter class.
*
* #throws Exception
*/
#Test
void testCanValidateToken() throws Exception {
String token = "a1.b2.c3";
when(jwtUtils.validateJwtToken(token)).thenReturn(true);
when(jwtUtils.getUserNameFromJwtToken(token)).thenReturn("username");
when(userDetailsServiceImpl.loadUserByUsername("username")).thenReturn(userDetails);
when(userDetails.getAuthorities()).thenReturn(null);
mvc.perform(get("/api/v1/ping").header("Authorization", "Bearer " + token)).andExpect(status().isOk());
}
/**
* Test cannot validate the token.
*
* Use /ping route because it is the route out of security, so we can
* concentrate to the AuthTokenFilter class.
*
* #throws Exception
*/
#Test
void testCannotValidateToken() throws Exception {
String token = "a1.b2.c3";
when(jwtUtils.validateJwtToken(token)).thenReturn(false);
mvc.perform(get("/api/v1/ping").header("Authorization", "Bearer " + token)).andExpect(status().isOk());
}
/**
* Test cannot validate the token if the header is missing.
*
* Use /ping route because it is the route out of security, so we can
* concentrate to the AuthTokenFilter class.
*
* #throws Exception
*/
#Test
void testCanReturnNullIfJwtIsMissingButOtherHeaderIsInPlace() throws Exception {
String token = "a1.b2.c3";
mvc.perform(get("/api/v1/ping").header("Authorization", "NotStartWithBearer " + token))
.andExpect(status().isOk());
}
}
I get
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.bitbank.config.AuthTokenFilterTest': Unsatisfied dependency expressed through field 'authTokenFilter'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.bitbank.config.AuthTokenFilter' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
(The test is here)
Or, in the RegisterControllerTest
#WebMvcTest(AuthController.class)
#TestPropertySource(locations = "classpath:application.properties", properties = "app.enableSubscription=false")
class RegisterControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
AuthTokenFilter authTokenFilter;
#MockBean
AuthEntryPointJwt authEntryPointJwt;
#MockBean
JwtUtils jwtUtils;
#Autowired
private ObjectMapper objectMapper;
#MockBean
UserDetailsServiceImpl userDetailsServiceImpl;
#MockBean
AuthenticationManager authenticationManager;
#MockBean
Authentication authentication;
#MockBean
SecurityContext securityContext;
#MockBean
private RolesService rolesService;
#Test
#WithMockUser(username = "username", authorities = { "ROLE_ADMIN" })
void cannotRegisterUserWhenSubscriptionsAreDisabled() throws Exception {
var userToSave = validUserEntity("username", "password");
var savedUser = validUserEntity("username", "a1.b2.c3");
when(userDetailsServiceImpl.post(userToSave)).thenReturn(savedUser);
mvc.perform(post("/api/v1/auth/register/").contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsBytes(userToSave))).andExpect(status().isBadRequest())
.andExpect(jsonPath("$.status", is("error")))
.andExpect(jsonPath("$.message", is("subscriptions disabled")));
}
private static UsersEntity validUserEntity(String username, String password) {
return UsersEntity.builder().username(username).password(password).build();
}
}
I get Error creating bean with name 'authController': Unsatisfied dependency expressed through field 'passwordEncoder'
SecurityConfig
Old:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsServiceImpl userDetailsServiceImpl;
#Autowired
private AuthEntryPointJwt authEntryPointJwt;
#Bean
AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Value("${app.allowedOrigins}")
private String allowedOriginsFromApplicationProperties;
/**
* Return allowedOrigins from application properties
*/
private String getAllowedOriginsFromApplicationProperties() {
return this.allowedOriginsFromApplicationProperties;
}
/**
* Return the allowed origins.
*
* #return
*/
private List<String> getAllowedOrigins() {
String[] allowedOrigins = this.getAllowedOriginsFromApplicationProperties().split(",");
return Arrays.asList(allowedOrigins);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsServiceImpl).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().exceptionHandling().authenticationEntryPoint(authEntryPointJwt).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
.mvcMatchers("/api/v1/ping").permitAll().mvcMatchers("/api/v1/auth/register").permitAll()
.mvcMatchers("/api/v1/auth/login").permitAll().mvcMatchers("/api-docs/**").permitAll()
.mvcMatchers("/swagger-ui/**").permitAll().mvcMatchers("/swagger-ui.html").permitAll()
.mvcMatchers("/documentation").permitAll().mvcMatchers("/").permitAll().anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Bean
CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.setAllowedOriginPatterns(getAllowedOrigins());
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
and new
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#RequiredArgsConstructor
public class SecurityConfiguration {
#Autowired
UserDetailsServiceImpl userDetailsServiceImpl;
#Autowired
AuthEntryPointJwt authEntryPointJwt;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Value("${app.allowedOrigins}")
private String allowedOriginsFromApplicationProperties;
/**
* Return allowedOrigins from application properties
*/
private String getAllowedOriginsFromApplicationProperties() {
return this.allowedOriginsFromApplicationProperties;
}
/**
* Return the allowed origins.
*
* #return
*/
private List<String> getAllowedOrigins() {
String[] allowedOrigins = this.getAllowedOriginsFromApplicationProperties().split(",");
return Arrays.asList(allowedOrigins);
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().exceptionHandling().authenticationEntryPoint(authEntryPointJwt).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
.mvcMatchers("/api/v1/ping").permitAll().mvcMatchers("/api/v1/auth/register").permitAll()
.mvcMatchers("/api/v1/auth/login").permitAll().mvcMatchers("/api-docs/**").permitAll()
.mvcMatchers("/swagger-ui/**").permitAll().mvcMatchers("/swagger-ui.html").permitAll()
.mvcMatchers("/documentation").permitAll().mvcMatchers("/").permitAll().anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*");
}
};
}
#Bean
CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.setAllowedOriginPatterns(getAllowedOrigins());
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
I cite the documentation for #WebMvcTest:
Annotation that can be used for a Spring MVC test that focuses only on Spring MVC components.
Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. #Controller, #ControllerAdvice, #JsonComponent, Converter/GenericConverter, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not #Component, #Service or #Repository beans).
This means that your SecurityConfiguration is never created because Spring only focuses on the web layer and not your custom components.
You have two options for fixing this:
1. Importing necessary beans
#WebMvcTest(PingController.class)
#Import(SecurityConfiguration.class)
class AuthTokenFilterTest {
Note, that you also need to import the configurations / components that your SecurityConfiguration relies on and all the classes these beans rely on, and so forth. So it would more likely have to look like this:
#WebMvcTest(PingController.class)
#Import({SecurityConfiguration.class, UserDetailsServiceImpl.class, AuthEntryPointJwt.class})
class AuthTokenFilterTest {
This will however decrease the maintainability of your test. If you added a new dependency to say UserDetailsServiceImpl, your test would break again because the bean can't be found.
2. Using #SpringBootTest
#SpringBootTest will create your whole application context with slight modifications for tests. The replacements would look like this:
#AutoConfigureMockMvc // <- included in WebMvcTest but not in SpringBootTest
#SpringBootTest
class AuthTokenFilterTest {
The disadvantage of this approach is that the tests are slower because the whole application context is created and not only the web layer. I prefer this approach as I think that maintainability is more important than the speed of the execution of the tests.
Related
Holla developers , i'm trying to frame the spring security process on my app using maven as wrapper , and right now i'm quite confused about how could i set verifications about user being logged or not in order to trigger specific functions on one of my controllers , lets say in my SecurityConfig file i set this :
...some imports....
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
// securedEnabled = true,
// jsr250Enabled = true,
prePostEnabled = true)public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
RenterService renterService;
#Autowired
UserDetailsServiceImpl userDetailsService;
#Autowired
private AuthEntryPointJwt unauthorizedHandler;
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public Authentication authentication(){
return authentication();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers("/cubancoder/multirenter/**","/v2/api-docs","/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**").permitAll()
.antMatchers("/api/test/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
Then lets say i enable a service and its implementation to get all products through my controller
SERVICE:
...some imports...
public interface ProductService {
Map<String,Object> getAllProducts()throws GeneralException;
}
SERVICE IMPLEMENTATION:
...some imports...
#Service
public class ProductServiceImpl implements ProductService{
public static final ModelMapper modelMapper = new ModelMapper();
#Autowired
ProductRepository productRepository;
#Autowired
ProductDtos productDtos;
#Autowired
AuthenticationValidation securityApp;
#Autowired
RenterDtos renterDtos;
#Autowired
RenterRepository renterRepository;
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
public Map<String,Object> getAllProducts() throws GeneralException {
Map<String,Object>dto=new HashMap<>();
List<Product>listProducts=productRepository.findAll();
if(auth==null){
dto.put("renter",null);//IF NO ONE IS LOGGED
}
else{
dto.put("renter",renterDtos.makeRenterDto(securityUser(auth)));IF THER IS A USER LOGGED
}
dto.put("list_ofProducts", listProducts.stream().map(service->productDtos.makeProductDto(service)).collect(Collectors.toList()));
return dto;
}
private Renter securityUser(Authentication auth)throws NotFoundException {
return renterRepository.findByRenterName(auth.getName()).orElseThrow(()->new NotFoundException("SError","EmailNotFound"));
}
}
No matter if user is logged or not always falls on user being null
if(auth==null){
dto.put("renter",null);//IF NO ONE IS LOGGED
}
Any idea about how could i improve this situation?
Thanks in advance!!
The problem is that there is a single instance of ProductServiceImpl which is initializing auth at startup time when there is no user in the SecurityContextHolder. Instead, you should initialize auth in the method which ensures that the auth variable is initialized at request time and there is a unique auth instance for every request (avoids race conditions). Something like this:
#Service
public class ProductServiceImpl implements ProductService{
public static final ModelMapper modelMapper = new ModelMapper();
#Autowired
ProductRepository productRepository;
#Autowired
ProductDtos productDtos;
#Autowired
AuthenticationValidation securityApp;
#Autowired
RenterDtos renterDtos;
#Autowired
RenterRepository renterRepository;
// remove auth as a member variable because it will be a shared variable across all requests and is null when the class initializes at startup
public Map<String,Object> getAllProducts() throws GeneralException {
// initialize auth as a stack variable so that it is no longer shared across requests and it is initialized when a user is in context (at request time)
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
...
}
// ...
}
i've a big problem and no idea how to solve it...
I need to use customAuthenticationManager for third party log-in in my spring boot application, but when i declare custom authenticator i get :
Handling error:
ClassCastException, java.lang.String cannot be cast to com.nexus.demooauth.models.User
If i use default authentication manager (the one that comes with spring boot) everything works fine.
Here is Websecurity.java
#Configuration
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Bean
public AuthenticationManager customAuthenticationManager() throws Exception {
return new CustomAuthenticationManager();
}
AuthorizationServerConfig.java
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
UserDetailsService customUserDetailsService;
#Autowired
DataSource dataSource;
#Autowired
private AuthenticationManager authenticationManager;
#Bean
public PasswordEncoder passwordEncoder() {
return new Plainencoder();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer configurer) throws Exception {
//configurer.userDetailsService(customUserDetailsService);
configurer.authenticationManager(authenticationManager);
configurer.tokenEnhancer(tokenEnhancer());
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("gigy").secret("secret").accessTokenValiditySeconds(8400)
.scopes("read", "write").authorizedGrantTypes("password", "refresh_token");
}
CustomAuthenticationManager.java
#Service
public class CustomAuthenticationManager implements AuthenticationManager{
private final Logger logger = LoggerFactory.getLogger(CustomAuthenticationManager.class);
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String pw = authentication.getCredentials().toString();
logger.info("was here" + username.toString() + " , " + pw.toString());
return new UsernamePasswordAuthenticationToken(username, pw, authentication.getAuthorities());
}
It actually prints in logger
2018-05-15 17:58:34.453 INFO 7212 --- [nio-8089-exec-1] c.n.d.s.CustomAuthenticationManager : was heretest , test
When debugging it breaks when returning new UsernamePasswordAuthenticationToken in some obfuscated class.
Actually found the anserw.
The problem was in CustomTokenEnhancer that was making this invalid conversion.
While attempting some tests using the Spring Boot framework, I am running into an issue with finding a Bean that the test unit depends on.
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'authServerApplication': Unsatisfied dependency expressed through field 'passwordEncoder'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.crypto.password.PasswordEncoder' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
My test class:
#RunWith(SpringRunner.class)
#DataJpaTest
#SpringBootTest
public class UserDetailsTest {
#Autowired
private TestEntityManager entityManager;
// #MockBean
// private PasswordEncoder passwordEncoder;
#Autowired
private UserRepository userRepo;
#Test
public void test() {
OAuthUser user = null;
this.entityManager.persist(new OAuthUser("Kelly", "Marchewa", "kmarchewa", "password"));
user = userRepo.findByUserName("kmarchewa");
System.out.println(user.getPassword());
assertThat(user.getUserName()).isEqualTo("kmarchewa");
}
}
If I uncomment the #MockBean portion, the code will compile fine. However, I want to test the repository on its ability to encode and decode passwords too. To my understanding of the documentation, the #SpringBootTest annotation should be able to automatically "pick-up" the #Configuration classes. I have a main #SpringBootApplication:
#SpringBootApplication
public class AuthServerApplication {
#Autowired
private PasswordEncoder passwordEncoder;
public static void main(String[] args) {
SpringApplication.run(AuthServerApplication.class, args);
}
#Bean
public CommandLineRunner demo(UserRepository repository) {
return(args) -> {
OAuthUser user = new OAuthUser();
user.setFirstName("Kelly");
user.setLastName("Marchewa");
user.setPassword(passwordEncoder.encode("Admin"));
user.setUserName("Admin");
// repository.save(user);
};
}
}
This Spring Boot Application depends on three other #Configuration classes: AppConfig, SecurityConfig, and AuthServerConfig. For this issue, the SecurityConfig and AppConfig classes are relevant (they include references to the PasswordEncoder bean).
AppConfig (partial)
#Configuration
public class AppConfig {
#Value("${spring.datasource.url}")
private String datasourceUrl;
#Value("${spring.datasource.driverClassName}")
private String dbDriverClassName;
#Value("${spring.datasource.username}")
private String dbUsername;
#Value("${spring.datasource.password}")
private String dbPassword;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/// more code here
}
SecurityConfig:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private OAuthUserDetailsService userService;
#Autowired
private PasswordEncoder passwordEncoder;
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
// Hash password
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService)
.passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic()
.realmName("test")
.and()
.csrf()
.disable();
}
}
The UserRepository class is very simple:
public interface UserRepository extends CrudRepository<OAuthUser, Long> {
public OAuthUser findByUserName(String name);
}
How can I ensure all required beans are found for my tests?
Thanks.
EDIT:
I receive the same error if I attempt to #Autowire the bean in my test class.
#Autowired
private PasswordEncoder passwordEncoder;
The problem is #DataJpaTest this annotation should be used only for Data repositories test and not full integration (which is what you are doing) as because it only persistence beans are created in context and not all you beans (the reason bean can not be found). What you need to do is use only #SpringBootTest and declared h2 as testing dependency, in that way a full recreation of your application will be created using in memory database
I'm trying to create a spring security configuration with two different AuthenticationProviders and exposing a rest interface to verify credentials (this is just used in the dev environment and will be replaced by an oAuth service in prod.) But when I inject the AuthenticationManager into the Controller, spring creates a default AuthenticationManager and injects it into the RestController. How can I make spring inject the AuthenticationManager configured in the WebSecurityConfigurationAdapter? I'm using spring-boot-starter-security:1.5.7.RELEASE. Here is my security configuration:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
#Configuration
public class LocalWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
private final DevUserDetailsService devUserDetailService;
private final ServiceUserDetailService serviceUserDetailService;
#Autowired
public LocalWebSecurityConfigurationAdapter(DevUserDetailsService devUserDetailService, ServiceUserDetailService serviceUserDetailService) {
this.devUserDetailService = devUserDetailService;
this.serviceUserDetailService = serviceUserDetailService;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests().antMatchers("/api/public/**").permitAll()
.antMatchers("/api/login").permitAll()
.antMatchers("/api/**").fullyAuthenticated()
.anyRequest().permitAll()
.and().exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint())
.and().httpBasic();
}
#Bean
public AuthenticationEntryPoint unauthorizedEntryPoint() {
return (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(devUserDetailService);
DaoAuthenticationProvider serviceUserAuthProvider = new DaoAuthenticationProvider();
serviceUserAuthProvider.setUserDetailsService(serviceUserDetailService);
serviceUserAuthProvider.setPasswordEncoder(passwordEncoder());
auth.authenticationProvider(serviceUserAuthProvider);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
And here is my RestController:
#RestController
#RequestMapping("/api/login")
public class LoginController {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private final AuthenticationManager authenticationManager;
public LoginController(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#RequestMapping(method = RequestMethod.POST)
public Map<String, String> login(#RequestBody Map<String, String> body) {
String user = body.get("user");
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, body.get("password"));
try {
authenticationManager.authenticate(token);
return Collections.singletonMap("status", "ok");
} catch (BadCredentialsException e) {
return Collections.singletonMap("status", "bad credentials");
} catch (AuthenticationException e) {
log.warn("Could not authenticate user {} because {}.", user, e.getMessage(), e);
return Collections.singletonMap("status", "general error");
}
}
}
And since you guys are probably experts in spring, is there a best practice to create different security configurations depending on the environment (using the profile) the code is running on without creating redundant code? I tried a super class, but spring didn't like that a lot.
I finally found a solution. By using configureGlobal inside my configuration class the AuthenticationManager gets shared across all spring managed Components.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, DevUserDetailsService devUserDetailService,
#Qualifier("serviceUserAuthenticationProvider") AuthenticationProvider serviceUserAuthProvider) throws Exception {
auth.userDetailsService(devUserDetailService);
auth.authenticationProvider(serviceUserAuthProvider);
}
For reusing configuration, I still didn't find a good solution. Creating an abstract “super configuration” for all the common configuration is creating troubles as soon as a method is annotated with #Bean and creating multiple WebSecurityConfigurerAdapter results in one overwriting the other, so if there is a best practice, I'm still interested in a proper solution. I've managed to do what I wanted, but it still feels like a little of a hack to me. For anyone stumbling across a similar issue I hope this helps a little.
Declare the bean in LocalWebSecurityConfigurationAdapter:
#Bean(name="appAuthenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
and inject it other components just like any other bean:
public LoginController(#Qualifier("appAuthenticationManager") AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
I am having a problem similar to PreAuthorize annotation doesn't work with jersey. I created a configuration class for Spring Security and the authentication works but the authorization does not.
Here is my code
SpringSecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#Order(1)
#ComponentScan({"com.foo.rest.resources.Template"})
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserService userService;
private final TokenAuthenticationService tokenAuthenticationService;
public SpringSecurityConfig() {
super(true);
this.userService = new UserService();
tokenAuthenticationService = new TokenAuthenticationService("tooManySecrets", userService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling().and()
.anonymous().and()
.servletApi().and()
.headers().cacheControl().and()
.authorizeRequests()
// Allow anonymous logins
.antMatchers("/auth/**").permitAll()
// All other request need to be authenticated
.anyRequest().authenticated().and()
// Custom Token based authentication based on the header previously given to the client
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService),
UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
#Override
public UserService userDetailsService() {
return userService;
}
#Bean
public TokenAuthenticationService tokenAuthenticationService() {
return tokenAuthenticationService;
}
}
and Template.java
#Component
#Path("/template")
#Produces(MediaType.APPLICATION_JSON)
public class Template {
#GET
#Secured("ROLE_EDITOR")
public User getTemplate() {
return new Template();
}
}
My guess is that the authentication is handled in the filter chain but it never comes back around after the authorization tag is reached. Any idea how to make this work?
I think your #ComponentScan is configured wrongly and doesn't pick the Template resource correctly.
According to #ComponentScan documentation the value is an alias for basePackages but you have given a Class instead of Package. Try and change it to look like following and see.
#ComponentScan({"com.foo.rest.resources.*"})
And make sure you haven't missed any steps in Jersey Spring Integration as per the documentation