BAEL-2741: JHipster authentication with external service (#7588)

* BAEL-2741: JHipster authentication using 3rd party service

* BAEL-2741: Remove Angular account mgmt routes

* BAEL-2741: Remove password reset register links from navbar

* BAEL-2741: Remove password and register links from login form

* BAEL-2741: Cleanup CustomAuthenticationManager code

* Update README.md

* BAEL-2741: Fix unit test
This commit is contained in:
Michael Pratt 2019-08-23 11:38:48 -06:00 committed by Josh Cummings
parent 721542f248
commit ce027707d0
7 changed files with 262 additions and 22 deletions

View File

@ -0,0 +1,126 @@
package com.baeldung.jhipster5.security;
import com.baeldung.jhipster5.domain.User;
import com.baeldung.jhipster5.security.dto.LoginRequest;
import com.baeldung.jhipster5.security.dto.LoginResponse;
import com.baeldung.jhipster5.service.UserService;
import com.baeldung.jhipster5.service.dto.UserDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.Collection;
import java.util.Collections;
import java.util.stream.Collectors;
@Component
public class CustomAuthenticationManager implements AuthenticationManager {
private final static Logger LOG = LoggerFactory.getLogger(CustomAuthenticationManager.class);
private final String REMOTE_LOGIN_URL = "https://example.com/login";
private final RestTemplate restTemplate = new RestTemplate();
@Autowired
private UserService userService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
LoginRequest loginRequest = new LoginRequest();
loginRequest.setUsername(authentication.getPrincipal().toString());
loginRequest.setPassword(authentication.getCredentials().toString());
try
{
ResponseEntity<LoginResponse> response =
restTemplate.postForEntity(
REMOTE_LOGIN_URL,
loginRequest,
LoginResponse.class);
if(response.getStatusCode().is2xxSuccessful())
{
//
// Need to create a new local user if this is the first time logging in; this
// is required so they can be issued JWTs. We can use this flow to also keep
// our local use entry up to date with data from the remote service if needed
// (for example, if the first and last name might change, this is where we would
// update the local user entry)
//
User user = userService.getUserWithAuthoritiesByLogin(authentication.getPrincipal().toString())
.orElseGet(() -> userService.createUser(createUserDTO(response.getBody(), authentication)));
return createAuthentication(authentication, user);
}
else
{
throw new BadCredentialsException("Invalid username or password");
}
}
catch (Exception e)
{
LOG.warn("Failed to authenticate", e);
throw new AuthenticationServiceException("Failed to login", e);
}
}
/**
* Creates a new authentication with basic roles
* @param auth Contains auth details that will be copied into the new one.
* @param user User object representing who is logging in
* @return Authentication
*/
private Authentication createAuthentication(Authentication auth, User user) {
//
// Honor any roles the user already has set; default is just USER role
// but could be modified after account creation
//
Collection<? extends GrantedAuthority> authorities = user
.getAuthorities()
.stream()
.map(a -> new SimpleGrantedAuthority(a.getName()))
.collect(Collectors.toSet());
UsernamePasswordAuthenticationToken token
= new UsernamePasswordAuthenticationToken(
user.getId(),
auth.getCredentials().toString(),
authorities);
return token;
}
/**
* Creates a new UserDTO with basic info.
* @param loginResponse Response from peloton login API
* @param authentication Contains user login info (namely username and password)
* @return UserDTO
*/
private UserDTO createUserDTO(LoginResponse loginResponse, Authentication authentication) {
UserDTO dto = new UserDTO();
dto.setActivated(true);
dto.setEmail(loginResponse.getEmail());
dto.setAuthorities(Collections.singleton(AuthoritiesConstants.USER));
dto.setFirstName(loginResponse.getFirstName());
dto.setLastName(loginResponse.getLastName());
return dto;
}
}

View File

@ -0,0 +1,30 @@
package com.baeldung.jhipster5.security.dto;
/**
* Simple DTO representing a login request to a remote service.
*/
public class LoginRequest {
private String username;
private String password;
public LoginRequest() {
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -0,0 +1,50 @@
package com.baeldung.jhipster5.security.dto;
/**
* Simple DTO representing the response of logging in using a remote service.
*/
public class LoginResponse {
private String username;
private String firstName;
private String lastName;
private String email;
public LoginResponse() {
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}

View File

@ -1,8 +1,8 @@
import { Routes } from '@angular/router';
import { activateRoute, passwordRoute, passwordResetFinishRoute, passwordResetInitRoute, registerRoute, settingsRoute } from './';
import { settingsRoute } from './';
const ACCOUNT_ROUTES = [activateRoute, passwordRoute, passwordResetFinishRoute, passwordResetInitRoute, registerRoute, settingsRoute];
const ACCOUNT_ROUTES = [settingsRoute];
export const accountState: Routes = [
{

View File

@ -114,12 +114,6 @@
<span>Settings</span>
</a>
</li>
<li *ngSwitchCase="true">
<a class="dropdown-item" routerLink="password" routerLinkActive="active" (click)="collapseNavbar()">
<fa-icon icon="clock" fixedWidth="true"></fa-icon>
<span>Password</span>
</a>
</li>
<li *ngSwitchCase="true">
<a class="dropdown-item" (click)="logout()" id="logout">
<fa-icon icon="sign-out-alt" fixedWidth="true"></fa-icon>
@ -132,12 +126,6 @@
<span>Sign in</span>
</a>
</li>
<li *ngSwitchCase="false">
<a class="dropdown-item" routerLink="register" routerLinkActive="active" (click)="collapseNavbar()">
<fa-icon icon="user-plus" fixedWidth="true"></fa-icon>
<span>Register</span>
</a>
</li>
</ul>
</li>
</ul>

View File

@ -30,14 +30,6 @@
</div>
<button type="submit" class="btn btn-primary">Sign in</button>
</form>
<p></p>
<div class="alert alert-warning">
<a class="alert-link" (click)="requestResetPassword()">Did you forget your password?</a>
</div>
<div class="alert alert-warning">
<span>You don't have an account yet?</span>
<a class="alert-link" (click)="register()">Register a new account</a>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,54 @@
package com.baeldung.jhipster5.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Collections;
/**
* AuthenticationManager used solely by unit tests.
*/
@Component
@Primary
public class MockAuthenticationManager implements AuthenticationManager
{
private final static Collection<? extends GrantedAuthority> ROLES =
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"));
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
UserDetails userDetails = userDetailsService.loadUserByUsername(authentication.getName());
if(userDetails == null || !passwordEncoder.matches(authentication.getCredentials().toString(), userDetails.getPassword()))
{
throw new BadCredentialsException("Invalid username/password");
}
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
authentication.getPrincipal().toString(),
authentication.getCredentials().toString(),
ROLES);
return token;
}
}