Olá pessoal, gostaria de trazer uma pequena contribuição/sugestão. Durante o módulo 2 Java e Spring Security: login com GitHub, Google e autenticação de 2 fatores, há uma tarefa no qual sugere a criação de um segundo OAuth Application no github e uma nova rota para cadastro do usuário utilizando o login do github, porém pensando um pouco bolei uma estratégia de usar apenas 1 OAuth Application que consiga realizar o login e o cadastro. Abaixo vou apresentar o código com as alterações caso mais alguém se interesse em implementar dessa forma.
No OAuth application mantive Authorization callback url em http://localhost:8080/login/github/authorized
Nesse caso o endpoint "/login/github/authorized" será responsável por checar se o usuário já está cadastrado no banco, caso não esteja, será cadastrado e no final retorna os tokens de acesso (accessToken e refreshToken).
Então o controller fica da seguinte forma:
@RestController
@RequestMapping("/login/github")
public class LoginGithubController {
@Autowired
private LoginGithubService loginGithubService;
@Autowired
private TokenManager tokenService;
@GetMapping
public ResponseEntity<Void> redirectToGithub(){
var url = loginGithubService.generateUrl();
var headers = new HttpHeaders();
headers.setLocation(URI.create(url));
return new ResponseEntity<>(headers, HttpStatus.FOUND);
}
@GetMapping("/authorized")
public ResponseEntity<LoginTokenDTO> oAuthUserAuthenticate(@RequestParam String code){
var user = loginGithubService.verifyGithubUserAndRegister(code);
var authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
String accessToken = tokenService.generateAccessToken((User) authentication.getPrincipal());
String refreshToken = tokenService.generateRefreshToken((User) authentication.getPrincipal());
return ResponseEntity.ok(new LoginTokenDTO(accessToken, refreshToken));
}
}
Quando o callback é chamado, o método verifyGithubUserAndRegister do loginGithubService vai verificar ser o usuário existe, caso exista só retorna o usuário, caso contrário, cadastra e retorna o usuário. Segue abaixo o LoginGithubService.
@Service
public class LoginGithubService {
...
@Autowired
private UserService userService;
public String generateUrl(){
return "https://github.com/login/oauth/authorize"+
"?client_id="+clientId +
"&redirect_uri="+redirectUri +
"&scope=read:user,user:email";
}
public UserRegisterDTO getGithubUserOAuthData(String code){
var accessToken = getToken(code);
var headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
var response = restClient.get()
.uri("https://api.github.com/user")
.headers(httpHeaders -> httpHeaders.addAll(headers))
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(Map.class);
var fullName = response.get("name").toString();
var nickname = response.get("login").toString();
var email = response.get("email").toString();
var password = UUID.randomUUID().toString();
return new UserRegisterDTO(email, password, fullName, nickname, null, null);
}
public User verifyGithubUserAndRegister(String code) {
var githubUser = getGithubUserOAuthData(code);
var user = userService.verifyUserByEmail(githubUser.email());
if(user != null)
return user;
return userService.registerVerifiedNewUser(githubUser);
}
private String getToken(String code) {
var response = restClient.post()
.uri("https://github.com/login/oauth/access_token")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(Map.of("code", code, "client_id", clientId,
"client_secret", clientSecret, "redirect_uri", redirectUri))
.retrieve()
.body(Map.class);
return response.get("access_token").toString();
}
}
No método verifyGithubUserAndRegister podemos observar que quando o usuário não é encontrado no banco, o userService.registerVerifiedNewUser salva um novo usuário, esse método é bem parecido com o que foi fornecido nas aulas, portanto vou omitir por limites de carateres no post do fórum. Qualquer dúvida ou sugestão fiquem avontade nas respostas.