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
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 extends GrantedAuthority> 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:
Welcome,
Bob
Connect your Account to Reddit
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.