Solucionado (ver solução)
Solucionado
(ver solução)
1
resposta

[Sugestão] Apenas um github OAtuh application para login e cadastro de usuário

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.

1 resposta
solução!

Oi, Ulysses! Como vai?

Agradeço por compartilhar.

Gostei da sua proposta de centralizar o fluxo de login e cadastro em um único OAuth Application, isso mostra uma visão interessante de simplificação da arquitetura e reaproveitamento do callback. Sua implementação no método verifyGithubUserAndRegister ficou clara e bem alinhada com a ideia de reduzir duplicidade de endpoints.

Continue explorando esse tipo de melhoria, pois pensar em fluxos mais enxutos é uma habilidade muito valorizada no dia a dia.

Alura Conte com o apoio da comunidade Alura na sua jornada. Abraços e bons estudos!