Découpler l’enregistrement de la connexion dans l’application Reddit

Découpler l'enregistrement de la connexion dans l'application Reddit

1. Vue d'ensemble

Dans ce didacticiel -we’ll replace the Reddit backed OAuth2 authentication process with a simpler, form-based login.

Nous serons toujours en mesure de connecter Reddit àthe application après la connexion, nous n'utiliserons tout simplement pas Reddit pour piloter notre flux de connexion principal.

2. Enregistrement utilisateur de base

Commençons par remplacer l'ancien flux d'authentification.

2.1. L'entitéUser

Nous allons apporter quelques modifications à l'entité User: rendez lesusername uniques, ajoutez un champpassword (temporaire):

@Entity
public class User {
    ...

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

    private String password;

    ...
}

2.2. Enregistrer un nouvel utilisateur

Ensuite, voyons comment enregistrer un nouvel utilisateur dans le backend:

@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);
    }
}

De toute évidence, il s’agit d’une opération de base de création pour l’utilisateur - pas de cloches ni de sifflets.

Voicithe 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. Gérer les exceptions

Et les simplesUserAlreadyExistsException:

public class UsernameAlreadyExistsException extends RuntimeException {

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

L'exception est traitée avecin 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. Une page de registre simple

Enfin - un simple frontalsignup.html:

Il convient de mentionner à nouveau qu’il ne s’agit pas d’un processus d’enregistrement à maturité - juste un flux très rapide. Pour un flux d'enregistrement complet, vous pouvez consulterthe main registration series ici par exemple.

3. Nouvelle page de connexion

Voici nosnew and simple login page:

Invalid username or password
Sign up

4. Configuration de sécurité

Maintenant, jetons un œil àthe 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);
    }
}

La plupart des choses sont assez simples, nous ne les détaillerons donc pas ici.

Et voici lesUserDetailsService personnalisés:

@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);
    }
}

Et voici nosPrincipal "UserPrincipal” personnalisés qui implémententUserDetails:

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;
    }
}

Remarque: Nous avons utilisé nosPrincipal «UserPrincipal” personnalisés au lieu desUser par défaut de Spring Security.

5. Authentifier Reddit

Maintenant que nous ne comptons plus sur Reddit pour notre flux d'authentification, nous devonsenable users to connect their accounts to Reddit après leur connexion.

Premièrement, nous devons modifier l'ancienne logique de connexion Reddit:

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

Et l'implémentation réelle - la méthodeconnectReddit():

@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);
}

Notez comment la logiqueredditLogin() est maintenant utilisée pour connecter le compte de l'utilisateur dans notre système avec son compte Reddit en obtenant lesAccessToken de l'utilisateur.

Quant au frontend, c'est assez simple:

Nous devons également nous assurer que les utilisateurs connectent leurs comptes à Reddit avant d'essayer de soumettre des publications:

@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. Conclusion

La petite application reddit est définitivement en train d'avancer.

L’ancien processus d’authentification, entièrement soutenu par Reddit, posait problème. Alors maintenant,we have a clean and simple form-based login tout en étant capable de connecter votre API Reddit dans le back-end.

Bon produit.