How to use BCryptPasswordEncoder on account registration and on account authentication in spring? - authentication

I have created a web application based on Spring-Boot 1.2.2.RELEASE. I also have a database table where I manage user credentials. It has the basic columns e.g. id, username, and password. Also added salt column where I plan to store randomly generated salts per account.
I'm trying to use Spring Security and having some difficulty with authentication using the encoded password. So basically I have a Register Controller:
#Autowired
private UserRepository userRepository;
private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ResponseEntity<User> register(#RequestParam(value = "email_address") String emailAddress, #RequestParam(value = "password") String password) {
String username = emailAddress;
String salt = KeyGenerators.string().generateKey();
User user = new User();
user.setUsername(emailAddress);
user.setEmailAddress(emailAddress);
user.setSalt(salt);
user.setPassword(passwordEncoder.encode(username+salt+password));
user.setCreatedBy(1);
user.setCreatedOn(new Date());
user.setLastUpdatedby(1);
user.setLastUpdatedOn(new Date());
user.setStartDate(new Date());
return new ResponseEntity<User>(this.userRepository.save(user), HttpStatus.OK);
}
Then I implemented UserDetailsService like this:
#Service
protected static class ApplicationUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = this.userRepository.findByUsername(username);
if (user == null) {
return null;
}
List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
String password = passwordEncoder.encode(username+user.getSalt()+user.getPassword());
return new org.springframework.security.core.userdetails.User(username, password, auth);
}
}
However, since BCryptPasswordEncoder generates new result everytime even if the input is the same, how will it be able to authenticate?
EDIT:
Just wanted to add that if I don't use any encoder and store the password as plain text, I am able to authenticate just fine. But of course that's not what I want to do.
UPDATE 1
Here's config for WebSecurityConfigurerAdapter. Inside it I have the configure method.
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
protected static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ApplicationUserDetailsService applicationUserDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(applicationUserDetailsService).passwordEncoder(passwordEncoder);
}
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
UPDATE 2
I got it working by extending DaoAuthenticationProvider. See updated code below.
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
protected static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ApplicationUserDetailsService applicationUserDetailsService;
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(applicationUserDetailsService);
authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
return authenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
Also had to modify my RegisterController as shown below.
#RestController
public class RegisterController {
#Autowired
private UserRepository userRepository;
private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ResponseEntity<User> register(#RequestParam(value = "email_address") String emailAddress, #RequestParam(value = "password") String password) {
User user = new User();
user.setUsername(emailAddress);
user.setEmailAddress(emailAddress);
user.setPassword(passwordEncoder.encode(password));
user.setSalt(KeyGenerators.string().generateKey());
user.setCreatedBy(1);
user.setCreatedOn(new Date());
user.setLastUpdatedby(1);
user.setLastUpdatedOn(new Date());
user.setStartDate(new Date());
return new ResponseEntity<User>(this.userRepository.save(user), HttpStatus.OK);
}
}
UPDATE 3
Forgot to include the updated UserDetailService implementation.
#Service
protected static class ApplicationUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = this.userRepository.findByUsername(username);
if (user == null) {
return null;
}
List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
String password = passwordEncoder.encode(user.getPassword());
return new org.springframework.security.core.userdetails.User(username, password, auth);
}
}
I might need to remove the salt column now since it's basically useless.
Hopefully this helps others as well.

Related

Spring webflux restdocs - By passing security for test cases

I am using Spring webflux security for my application and trying to write Spring webflux restdocs. Getting unauthorized error for test cases. Is there anyway to by pass security for rest doc test cases? Is it possible to control thru property?
#ExtendWith({ SpringExtension.class, RestDocumentationExtension.class })
#WebFluxTest({ RegistrationRequesttHandler.class })
#AutoConfigureWebTestClient(timeout = "100000")
class RegistrationRequestHandlerTest {
#Autowired
ApplicationContext context;
#MockBean
private OrgRepository orgRepository;
#MockBean
private UserRepository usrRepository;
#Captor
private ArgumentCaptor<Organization> orgInputCaptor;
#Captor
private ArgumentCaptor<Mono<Organization>> orgMonoInputCaptor;
#Captor
private ArgumentCaptor<User> usrInputCaptor;
private WebTestClient webTestClient;
#BeforeEach
void setUp(RestDocumentationContextProvider restDocumentation) {
webTestClient = WebTestClient.bindToApplicationContext(context).configureClient()
.filter(documentationConfiguration(restDocumentation)).responseTimeout(Duration.ofMillis(100000))
.build();
}
#Test
public void testRegister() {
final Register register = new Register();
final Organization org = new Organization();
final User usr = new User();
given(orgRepository.save(orgInputCaptor.capture())).willReturn(Mono.just(org));
given(usrRepository.save(usrInputCaptor.capture())).willReturn(Mono.just(usr));
webTestClient.mutateWith(csrf()).post().uri(REGISTER_PATH).contentType(APPLICATION_JSON).bodyValue(register).exchange()
.expectStatus().is2xxSuccessful().expectBody();
StepVerifier.create(orgMonoInputCaptor.getValue()).expectNext(org).expectComplete();
then(usrRepository).should().save(usrInputCaptor.capture());
}
private String buildRegister() {
// TODO Auto-generated method stub
return null;
}
}
Here, I am testing /register api which is been set to permitAll().
#Bean
public SecurityWebFilterChain securitygWebFilterChain(ServerHttpSecurity http) {
return http.authorizeExchange().matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.pathMatchers("/register", "/login").permitAll()
.anyExchange().authenticated()
.and().formLogin()
.securityContextRepository(securityContextRepository())
.and()
.exceptionHandling()
.accessDeniedHandler(new HttpStatusServerAccessDeniedHandler(HttpStatus.BAD_REQUEST))
.and().csrf().disable()
.build();
}
But still getting 401 error for testRegister. Do we need to create test bean for SecurityWebFilterChain also with permitAll for register API?
By default, spring-security enabled for Test classes, in order to test public APIs we have to skip security by excluding AutoConfiguration.
#ExtendWith(SpringExtension.class)
#WebFluxTest(controllers = AuthController.class, excludeAutoConfiguration = ReactiveSecurityAutoConfiguration.class)
public class AuthControllerTest {
#Autowired
private WebTestClient webClient;
#MockBean
AuthService authService;
#Test
public void shouldSignUp() {
UserDto userDto = UserDto.builder()
.firstName("firstName")
.lastName("lastName")
.phone("phone")
.password("password")
.email(TEST_EMAIL)
.role(UserRoles.ADMIN)
.build();
when(authService.signup(any(UserDto.class))).thenReturn(Mono.just(userDto));
webClient
.post().uri("/auth/v1/signup")
.bodyValue(userDto)
.exchange()
.expectStatus()
.isOk()
.expectBody(UserDto.class);
}
}
https://github.com/Brajendra/springboot-reactive-starter-kit

Spring-security/Grails app - Custom WebSecurity configurations not getting called

My project is based on Grail 2.5.6 and Spring plugins. I'm trying to create a custom auth provider, filter and token extending their respective basic classes.
this.getAuthenticationManager().authenticate(authRequest)
In my filter the authentication manager is always null. So, it throws cannot invoke authenticate() on a null object. When I debug on the authenticationManager, it lists other provider names but my custom one.
Here is my custom web security config
#Configuration
#EnableGlobalMethodSecurity(securedEnabled=true)
public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
OrbisAuthenticationProvider orbisAuthenticationProvider
public CustomWebSecurityConfig() {
super()
log.debug "configure custom security"
print("configure custom security")
}
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
print("configure method 1")
log.debug "configure method 1"
auth.authenticationProvider(orbisAuthenticationProvider)
}
#Bean(name= BeanIds.AUTHENTICATION_MANAGER)
#Override
AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean()
}
#Bean
OrbisAuthenticationFilter orbisAuthenticationProvider() throws Exception {
log.debug "orbis Authentication provider"
OrbisAuthenticationProvider orbisAuthenticationProvider = new OrbisAuthenticationProvider(authenticationManagerBean())
return orbisAuthenticationProvider
}
#Bean
#Autowired
public OrbisAuthenticationFilter orbisAuthenticationFilter() throws Exception {
print("configure orbis filtr")
OrbisAuthenticationFilter oaf = new OrbisAuthenticationFilter()
oaf.setAuthenticationManager(authenticationManagerBean())
oaf.setFilterProcessesUrl("j_orbis_security_check")
oaf.setUsernameParameter("email")
oaf.setPasswordParameter("password")
oaf.setAuthenticationSuccessHandler(new SavedRequestAwareAuthenticationSuccessHandler()
.setDefaultTargetUrl("/oauth/authorize"))
oaf.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler()
.setDefaultFailureUrl("/loginWithOrbis"))
oaf.afterPropertiesSet()
return oaf
}
}
On debugging, it doesn't look like any of these methods are getting called. The annotations don't seem enough to get picked up. I had tried #ComponentScan too.
Do I have to inject this security config somewhere? How do I get authManager to be available in my filter?
OrbisAuthFilter
class OrbisAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
// #Autowired
OrbisAuthenticationProvider orbisAuthenticationProvider
OrbisAuthenticationFilter() {
super("/j_orbis_security_check")
orbisAuthenticationProvider = new OrbisAuthenticationProvider()
}
void afterPropertiesSet() {
assert authenticationManager != null, 'authenticationManager must be specified'
}
#Override
Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("email")
String password = request.getParameter("password")
String accessCode = request.getParameter("accessCode")
OrbisAuthenticationToken authRequestForAuthentication = new OrbisAuthenticationToken(username, password, accessCode)
// This throws error because getAuthenticationManager returns null
// authRequestForAuthentication = this.getAuthenticationManager.authenticate(authRequestForAuthentication)
//This works if I instantiate the orbis provider object in the constructor
authRequestForAuthentication = this.orbisAuthenticationProvider.authenticate(authRequestForAuthentication)
SecurityContextHolder.getContext().setAuthentication(authRequestForAuthentication)
return authRequestForAuthentication
}
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
#Override
#Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
}
OrbisAuthProvider
class OrbisAuthenticationProvider implements AuthenticationProvider {
#Override
Authentication authenticate(Authentication authentication) throws AuthenticationException {
OrbisAuthenticationToken orbisAuth = (OrbisAuthenticationToken) authentication
String username = orbisAuth.principal
String password = orbisAuth.credentials
String orbisAccessCode = orbisAuth.orbisAccessCode
def urlToUse = 'https://coopstatus.neu.edu/sail_api/full.aspx?' + 'ac=' + orbisAccessCode + '&e='+ username + '&p=' + password
HttpClient httpClient = DefaultHttpClient.newInstance()
HttpGet getRequest = new HttpGet(urlToUse)
HttpResponse httpResponse = httpClient.execute(getRequest)
JSONObject orbisResponse = new JSONObject(httpResponse.getEntity().getContent().getText())
// if(orbisResponse.get("IsFound")) {
// //Return error not authenticated
// }
Collection<GrantedAuthority> orbisUserGrantedAuthorities = getLDAPUserAuthorities(orbisResponse.get("Email"))
orbisAuth = new OrbisAuthenticationToken(username, password, orbisAccessCode, orbisUserGrantedAuthorities)
return orbisAuth
}
private Collection<GrantedAuthority> getLDAPUserAuthorities(String username) {
LDAPUserDetails currentLdapUserDetails
try {
currentLdapUserDetails = new LDAPUserDetailsService().loadUserByOrbisUsername(username)
log.debug currentLdapUserDetails
} catch(org.springframework.security.core.userdetails.UsernameNotFoundException e) {
log.error("User " + username + " not found in ldap", e)
}
Collection<GrantedAuthority> authorities = new ArrayList<>()
for (String authority : currentLdapUserDetails.authorities) {
authorities.add(new SimpleGrantedAuthority(authority))
}
return authorities
}
#Override
public boolean supports(Class<?> authentication) {
return (OrbisAuthenticationToken.class
.isAssignableFrom(authentication));
}
}
Resources.groovy
import edu.neu.security.OrbisAuthenticationFilter
import edu.neu.security.OrbisAuthenticationProvider
beans = {
userDetailsService(edu.neu.security.LDAPUserDetailsService)
orbisAuthenticationProvider(OrbisAuthenticationProvider)
orbisAuthenticationFilter(OrbisAuthenticationFilter) {
orbisAuthenticationProvider = ref("orbisAuthenticationProvider")
requiresAuthenticationRequestMatcher = ref('filterProcessUrlRequestMatcher')
// This throws error during startup. Unable to init bean
// authenicationManager = ref("authenicationManager")
}
myOAuth2ProviderFilter(OAuth2ProviderFilters) {
//grailsApplication = ref('grailsApplication')
// properties
}
}
I followed some of the concepts from this project: https://github.com/ppazos/cabolabs-ehrserver/
Even if the whole process is executed and securityContext is set with authenticated, when I hit oauth/authorize to get Authorization_Code, it redirects back to '/login/auth'. It still doesn't know that a user is already authenticated.
When you add an authentication provider to the AuthenticationManagerBuilder bean (which comes from AuthenticationConfiguration), the authentication manager bean you declare is not used.
Try:
#Configuration
#EnableGlobalMethodSecurity(securedEnabled=true)
public class CustomWebSecurityConfig {
OrbisAuthenticationProvider lwoAuthProvider;
public CustomWebSecurityConfig() {
//
}
#Bean(name= BeanIds.AUTHENTICATION_MANAGER)
AuthenticationManager authenticationManagerBean() throws Exception {
return new ProviderManager(Arrays.asList(lwoAuthProvider));
}
Your AuthenticationManager bean should get picked up and will be used for method security. You can also #Autowire it in your filter if it is being managed by Spring, or #Autowire it in the #Configuration class that instantiates your filter.
NOTE: the above class WILL NOT create any of the Spring Security filters.
(The filter chain wasn't being created anyway - you didn't annotate your class with #EnableWebSecurity)

Spring TestRestTemplate authentication

I am trying to build Spring Boot test to test rest API, so that I can get Principal from the request and use that to identify the user.
Server returns
{"timestamp":1502014507361,"status":403,"error":"Forbidden","message":"Access
Denied","path":"/hello"}
What am I missing here?
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RestTemplateTest {
#Autowired
TestRestTemplate testRestTemplate;
#Test
public void testit() {
testRestTemplate.withBasicAuth("user", "password");
String responsePayload = testRestTemplate.getForObject("/hello", String.class);
}
#RestController
public class GreetingController {
#RequestMapping("/hello")
public String heipat(Principal principal) {
String string = "hello there";
return string;
}
#Configuration
#EnableWebSecurity
static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests().anyRequest().hasRole("USER");
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
You need to be authenticated first. like requesting a /login API.
Also you need to make the login API accessible by everyone by doing this:
http.csrf().disable().authorizeRequests()
.antMatchers("/login").permitAll()
When you includes WebSecurityConfig you will have basic usernamerAndPassowrd authentication.

Spring Security Basic authentication for a single path next to token authentication

I have a custom ResourceServerTokenServices in place:
#Configuration
public class CloudSecurityConfig {
#Bean
protected MyResourceServerTokenServices() {
return new MyResourceServerTokenServices();
}
}
Then I have follogin ResourceServerConfigurerAdapter:
#Configuration
#EnableWebSecurity
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityResourceConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
http.authorizeRequests().accessDecisionManager(accessDecisionManager())
.antMatchers("/h2-console/**").anonymous()
.antMatchers("/health/**").permitAll()
.antMatchers("/v*/api-docs").permitAll().anyRequest()
.authenticated().and().httpBasic().and().headers()
.frameOptions().disable();
}
#Bean
protected UnanimousBased accessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> voterList = new ArrayList<>();
WebExpressionVoter expressionVoter = new WebExpressionVoter();
expressionVoter.setExpressionHandler(new OAuth2WebSecurityExpressionHandler());
voterList.add(expressionVoter);
voterList.add(new AuthenticatedVoter());
return new UnanimousBased(voterList);
}
}
Now I need to add basic authentication (with inMemory credentials) for one single endpoint (lets say /myEndpoint**). How can I achieve this?
Thanks!

Password encoding with Spring Data REST

How should I encode automatically the subbmitted plain password field of my entity with Spring Data REST?
I'm using BCrypt encoder and I want to automatically encode the request's password field, when the client send it via POST, PUT and PATCH.
#Entity
public class User {
#NotNull
private String username;
#NotNull
private String passwordHash;
...
getters/setters/etc
...
}
First I tried to solve with #HandleBeforeCreate and #HandleBeforeSave event listeners but the User in it's argument is already merged, so I can't make any difference between the User's new password, or the old passwordHash:
#HandleBeforeSave
protected void onBeforeSave(User user) {
if (user.getPassword() != null) {
account.setPassword(passwordEncoder.encode(account.getPassword()));
}
super.onBeforeSave(account);
}
Is that possible, to use #Projection and SpEL on a setter method?
You can implement a Jackson JsonDeserializer:
public class BCryptPasswordDeserializer extends JsonDeserializer<String> {
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String encodedPassword = encoder.encode(node.asText());
return encodedPassword;
}
}
And apply it to your JPA Entity property:
// The value of the password will always have a length of
// 60 thanks to BCrypt
#Size(min = 60, max = 60)
#Column(name="password", nullable = false, length = 60)
#JsonDeserialize(using = BCryptPasswordDeserializer.class )
private String password;
Modifying setter method of password field is sufficient, as shown below:
public void setPassword(String password) {
PasswordEncoder encoder = new BCryptPasswordEncoder();
this.password = encoder.encode(password);
}
Refer:
https://github.com/charybr/spring-data-rest-acl/blob/master/bookstore/src/main/java/sample/sdr/auth/bean/UserEntity.java
Some enhancement to #robgmills JsonDeserializer solution:
In Spring 5 introduce DelegatingPasswordEncoder. It is more flexible, see spring docs.
It is not nesessary to create PasswordEncoder every time at deserialization.
A big projects may has several JsonDeserializer's - better make them inner classes.
Usually encoding password hidden for get request. I've used #JsonProperty(access = JsonProperty.Access.WRITE_ONLY), see https://stackoverflow.com/a/12505165/548473
For Spring Boot code looks like:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public static final PasswordEncoder PASSWORD_ENCODER = PasswordEncoderFactories.createDelegatingPasswordEncoder();
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(PASSWORD_ENCODER);
}
....
public class JsonDeserializers {
public static class PasswordDeserializer extends JsonDeserializer<String> {
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
String rawPassword = node.asText();
return WebSecurityConfig.PASSWORD_ENCODER.encode(rawPassword);
}
}
...
#Entity
public class User ...
#Column(name = "password")
#Size(max = 256)
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#JsonDeserialize(using = JsonDeserializers.PasswordDeserializer.class)
private String password;
...