Entkopplung Anmeldung vom Login in der Reddit App

Entkopplung der Registrierung vom Login in der Reddit App

1. Überblick

In diesem Tutorial -we’ll replace the Reddit backed OAuth2 authentication process with a simpler, form-based login.

Nach der Anmeldung können wir Reddit weiterhin bis zuthe application einbinden. Wir verwenden Reddit jedoch nicht, um unseren Hauptanmeldefluss zu steuern.

2. Grundlegende Benutzerregistrierung

Ersetzen wir zunächst den alten Authentifizierungsablauf.

2.1. DieUser-Entität

Wir werden einige Änderungen an der Benutzerentität vornehmen:usernameeindeutig machen,password Feld hinzufügen (temporär):

@Entity
public class User {
    ...

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

    private String password;

    ...
}

2.2. Registrieren Sie einen neuen Benutzer

Weiter - sehen wir uns an, wie Sie einen neuen Benutzer im Backend registrieren:

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

Dies ist offensichtlich eine grundlegende Erstellungsoperation für den Benutzer - kein Schnickschnack.

Hier sindthe 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. Umgang mit Ausnahmen

Und die einfachenUserAlreadyExistsException:

public class UsernameAlreadyExistsException extends RuntimeException {

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

Die Ausnahme wird mitin the main exception handler of the application behandelt:

@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. Eine einfache Registerseite

Schließlich - ein einfaches Front-Endsignup.html:

Es ist noch einmal erwähnenswert, dass dies kein ausgereifter Registrierungsprozess ist - nur ein sehr schneller Ablauf. Für einen vollständigen Registrierungsablauf können Sie hier am Beispielthe main registration series auschecken.

3. Neue Anmeldeseite

Hier sind unserenew and simple login page:

Invalid username or password
Sign up

4. Sicherheitskonfiguration

Schauen wir uns jetztthe new security configuration an:

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

Die meisten Dinge sind ziemlich einfach, daher werden wir sie hier nicht im Detail behandeln.

Und hier sind die benutzerdefiniertenUserDetailsService:

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

Und hier ist unser benutzerdefiniertesPrincipalUserPrincipal”, dasUserDetails implementiert:

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

Hinweis: Wir haben unsere benutzerdefiniertenPrincipalUserPrincipal” anstelle der Spring Security-StandardeinstellungUser verwendet.

5. Reddit authentifizieren

Da wir uns für unseren Authentifizierungsablauf nicht mehr auf Reddit verlassen, müssen wir nach der Anmeldungenable users to connect their accounts to Reddit eingeben.

Zunächst müssen wir die alte Reddit-Anmeldelogik ändern:

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

Und die eigentliche Implementierung - dieconnectReddit()-Methode:

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

Beachten Sie, wie dieredditLogin()-Logik jetzt verwendet wird, um das Benutzerkonto in unserem System mit seinem Reddit-Konto zu verbinden, indem dieAccessToken des Benutzers abgerufen werden.

Das Frontend ist ganz einfach:

Wir müssen auch sicherstellen, dass Benutzer ihre Konten mit Reddit verbinden, bevor Sie versuchen, Beiträge zu übermitteln:

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

Die kleine reddit App ist definitiv in Bewegung.

Der alte Authentifizierungsablauf, der vollständig von Reddit unterstützt wurde, verursachte einige Probleme. Jetzt istwe have a clean and simple form-based login, während Sie weiterhin in der Lage sind, Ihre Reddit-API im Back-End zu verbinden.

Gutes Zeug.