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:
parent
721542f248
commit
ce027707d0
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 = [
|
||||
{
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user