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
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á nossoPrincipal “UserPrincipal” 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 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;
}
}
Nota: Usamos nossoPrincipal “UserPrincipal” 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:
Welcome,
Bob
Connect your Account to Reddit
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.