Отключение регистрации от входа в приложение Reddit

Разделение регистрации от входа в приложение Reddit

1. обзор

В этом руководстве -we’ll replace the Reddit backed OAuth2 authentication process with a simpler, form-based login.

Мы по-прежнему сможем подключить Reddit кthe application после входа в систему, мы просто не будем использовать Reddit для управления нашим основным потоком входа.

2. Базовая регистрация пользователя

Во-первых, давайте заменим старую процедуру аутентификации.

2.1. СущностьUser

Мы внесем несколько изменений в сущность User: сделаемusername уникальным, добавим полеpassword (временное):

@Entity
public class User {
    ...

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

    private String password;

    ...
}

2.2. Зарегистрируйте нового пользователя

Далее - давайте посмотрим, как зарегистрировать нового пользователя в серверной части:

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

Очевидно, это базовая операция создания для пользователя - никаких наворотов.

Вот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. Работа с исключениями

И простойUserAlreadyExistsException:

public class UsernameAlreadyExistsException extends RuntimeException {

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

Исключение составляетin 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. Простая страница регистрации

Напоследок - простой интерфейсsignup.html:

Стоит еще раз упомянуть, что это еще не полностью зрелый процесс регистрации - это очень быстрый процесс. Чтобы увидеть полный процесс регистрации, вы можете проверитьthe main registration series здесь, например.

3. Новая страница входа

Вот нашnew and simple login page:

Invalid username or password
Sign up

4. Конфигурация безопасности

Теперь посмотрим на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);
    }
}

Большинство вещей довольно просты, поэтому мы не будем их здесь подробно рассматривать.

А вот пользовательскийUserDetailsService:

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

А вот наш собственныйPrincipal «UserPrincipal”, который реализуетUserDetails:

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

Примечание. Мы использовали собственныйPrincipal «UserPrincipal” вместоUser по умолчанию Spring Security.

5. Аутентифицировать Reddit

Теперь, когда мы больше не полагаемся на Reddit в процессе аутентификации, нам нужно выполнитьenable users to connect their accounts to Reddit после входа в систему.

Во-первых, нам нужно изменить старую логику входа в Reddit:

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

И собственно реализация - методconnectReddit():

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

Обратите внимание, как логикаredditLogin() теперь используется для соединения учетной записи пользователя в нашей системе с его учетной записью Reddit путем полученияAccessToken пользователя.

Что касается фронтенда - все очень просто:

Нам также необходимо убедиться, что пользователи подключают свои учетные записи к Reddit, прежде чем пытаться отправлять сообщения:

@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. Заключение

Небольшое приложение Reddit определенно движется вперед.

Старый поток аутентификации, полностью поддерживаемый Reddit, вызывал некоторые проблемы. Итак, теперьwe have a clean and simple form-based login все еще может подключить ваш Reddit API в бэкенде.

Хорошая вещь.