Como dissociar o registro do login no aplicativo Reddit

Como dissociar o registro do login no aplicativo Reddit

1. Visão geral

Neste tutorial -we’ll replace the Reddit backed OAuth2 authentication process with a simpler, form-based login.

Ainda seremos capazes de conectar o Reddit atéthe application depois de fazer login, apenas não usaremos o Reddit para direcionar nosso fluxo de login principal.

2. Registro de usuário básico

Primeiro, vamos substituir o antigo fluxo de autenticação.

2.1. A EntidadeUser

Faremos algumas alterações na entidade User: torne ousername único, adicione um campopassword (temporário):

@Entity
public class User {
    ...

    @Column(nullable = false, unique = true)
    private String username;

    private String password;

    ...
}

2.2. Registrar um novo usuário

A seguir - vamos ver como registrar um novo usuário no back-end:

@Controller
@RequestMapping(value = "/user")
public class UserController {

    @Autowired
    private UserService service;

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.OK)
    public void register(
      @RequestParam("username") String username,
      @RequestParam("email") String email,
      @RequestParam("password") String password)
    {
        service.registerNewUser(username, email, password);
    }
}

Obviamente, esta é uma operação básica de criação para o usuário - sem sinos e assobios.

Aqui estáthe actual implementation, in the service layer:

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PreferenceRepository preferenceReopsitory;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public void registerNewUser(String username, String email, String password) {
        User existingUser = userRepository.findByUsername(username);
        if (existingUser != null) {
            throw new UsernameAlreadyExistsException("Username already exists");
        }

        User user = new User();
        user.setUsername(username);
        user.setPassword(passwordEncoder.encode(password));
        Preference pref = new Preference();
        pref.setTimezone(TimeZone.getDefault().getID());
        pref.setEmail(email);
        preferenceReopsitory.save(pref);
        user.setPreference(pref);
        userRepository.save(user);
    }
}

2.3. Lidando com exceções

E oUserAlreadyExistsException simples:

public class UsernameAlreadyExistsException extends RuntimeException {

    public UsernameAlreadyExistsException(String message) {
        super(message);
    }
    public UsernameAlreadyExistsException(String message, Throwable cause) {
        super(message, cause);
    }
}

A exceção é tratada comin the main exception handler of the application:

@ExceptionHandler({ UsernameAlreadyExistsException.class })
public ResponseEntity
  handleUsernameAlreadyExists(RuntimeException ex, WebRequest request) {
    logger.error("400 Status Code", ex);
    String bodyOfResponse = ex.getLocalizedMessage();
    return new
      ResponseEntity(bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST);
}



2.4. Uma página de registro simples

Finalmente - um front-end simplessignup.html:

Vale a pena mencionar novamente que este não é um processo de registro totalmente maduro - apenas um fluxo muito rápido. Para um fluxo de registro completo, você pode verificarthe main registration series aqui no exemplo.

3. Nova página de login

Aqui está nossonew and simple login page:

Invalid username or password
Sign up

4. Configuração de segurança

Agora - vamos dar uma olhada emthe new security configuration:

@Configuration
@EnableWebSecurity
@ComponentScan({ "org.example.security" })
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(encoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            ...
            .formLogin()
            .loginPage("/")
            .loginProcessingUrl("/j_spring_security_check")
            .defaultSuccessUrl("/home")
            .failureUrl("/?error=true")
            .usernameParameter("username")
            .passwordParameter("password")
            ...
    }

    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder(11);
    }
}

A maioria das coisas é bastante simples, por isso não as examinaremos em detalhes aqui.

E aqui está oUserDetailsService personalizado:

@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException(username);
        }
        return new UserPrincipal(user);
    }
}

E aqui está nossoPrincipalUserPrincipal” personalizado que implementaUserDetails:

public class UserPrincipal implements UserDetails {

    private User user;

    public UserPrincipal(User user) {
        super();
        this.user = user;
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public Collection getAuthorities() {
        return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

Nota: Usamos nossoPrincipalUserPrincipal” personalizado em vez do padrãoUser do Spring Security.

5. Autenticar Reddit

Agora que não contamos mais com o Reddit para nosso fluxo de autenticação, precisamosenable users to connect their accounts to Reddit depois que eles fizerem login.

Primeiro - precisamos modificar a antiga lógica de login do Reddit:

@RequestMapping("/redditLogin")
public String redditLogin() {
    OAuth2AccessToken token = redditTemplate.getAccessToken();
    service.connectReddit(redditTemplate.needsCaptcha(), token);
    return "redirect:home";
}

E a implementação real - o métodoconnectReddit():

@Override
public void connectReddit(boolean needsCaptcha, OAuth2AccessToken token) {
    UserPrincipal userPrincipal = (UserPrincipal)
      SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    User currentUser = userPrincipal.getUser();
    currentUser.setNeedCaptcha(needsCaptcha);
    currentUser.setAccessToken(token.getValue());
    currentUser.setRefreshToken(token.getRefreshToken().getValue());
    currentUser.setTokenExpiration(token.getExpiration());
    userRepository.save(currentUser);
}

Observe como a lógicaredditLogin() agora é usada para conectar a conta do usuário em nosso sistema com sua conta do Reddit, obtendoAccessToken do usuário.

Quanto ao front-end - isso é bastante simples:

Também precisamos garantir que os usuários conectem suas contas ao Reddit antes de tentar enviar postagens:

@RequestMapping("/post")
public String showSubmissionForm(Model model) {
    if (getCurrentUser().getAccessToken() == null) {
        model.addAttribute("msg", "Sorry, You did not connect your account to Reddit yet");
        return "submissionResponse";
    }
    ...
}

6. Conclusão

O pequeno aplicativo reddit está definitivamente avançando.

O antigo fluxo de autenticação - totalmente suportado pelo Reddit - estava causando alguns problemas. Agora,we have a clean and simple form-based login enquanto ainda consegue conectar sua API do Reddit no back end.

Coisa boa.