Я пытаюсь обернуть голову вокруг OAuth2 и Spring Security OAuth, особенно для службы OAuth Provider. Я пытаюсь реализовать следующее:
- Поставщик OAuth
- Сервер ресурсов (веб-службы RESTful, которые должны быть защищены с использованием OAuth Provider (1))
- Веб-клиент (приложение веб-клиента, защищенное с помощью Spring Безопасность, но должно использовать OAuth Provider (1) для аутентификации пользователя
- Собственные мобильные клиенты (Android и iOS), которые также должны использовать OAuth Provider (1) для аутентификации
Все эти модули независимы друг от друга, т.е. разделены в разных проектах и будут размещаться в разных доменах, таких как (1) http://oauth.web.com, (2) http://rest.web.com, (3) http://web.com
Мои два вопроса:
а. Как реализовать проект веб-клиента, чтобы при входе пользователя на защищенную страницу или нажатии кнопки "Войти", перенаправление на URL-адрес провайдера OAuth, вход в систему и аутентификация на веб-клиенте со всеми ролями пользователя, а также нужно знать, какой клиент был использован. @EnableResourceServer
(так же, как реализован Resource Server, см. код ниже) в этом проекте, чтобы получить информацию о пользователе? Нужно ли мне управлять токеном доступа и всегда включать его в вызов на сервере ресурсов или это можно сделать как-то автоматически?
В. Каков наилучший способ обеспечения безопасности в мобильных приложениях, которые я буду разрабатывать. Должен ли я использовать пароль grand для этой аутентификации, так как приложения будут созданы мной, где у меня будет имя пользователя и пароль на собственном экране, а затем отправьте на сервер в качестве базовой проверки подлинности через SSL? Есть ли какие-либо образцы, на которых я могу взглянуть на этот разговор с Spring Security OAuth и вернуть данные пользователя.
Вот моя реализация проекта OAuth (1) и ресурсного проекта (2):
1. Поставщик OAuth
Конфигурации сервера OAuth2 (большая часть кода была взята из ЗДЕСЬ)
@Configuration
@EnableAuthorizationServer
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Autowired
DataSource dataSource;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore())
.approvalStore(approvalStore())
.authorizationCodeServices(authorizationCodeServices())
;
}
@Bean
public JdbcClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
@Bean
public ApprovalStore approvalStore() {
return new JdbcApprovalStore(dataSource);
}
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.checkTokenAccess("permitAll()");
}
}
Конфигурация веб-безопасности
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); // TODO. Enable this!!!
http.authorizeRequests()
.and()
.formLogin()
// .loginPage("/login") // manually defining page to login
// .failureUrl("/login?error") // manually defining page for login error
.usernameParameter("email")
.permitAll()
.and()
.logout()
// .logoutUrl("/logout")
.logoutSuccessUrl("/")
.permitAll();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(customUserDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
UserDetailsService (customUserDetailsService)
@Service
public class CustomUserDetailsService implements UserDetailsService{
private final UserService userService;
@Autowired
public CustomUserDetailsService(UserService userService) {
this.userService = userService;
}
public Authority loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userService.getByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(String.format("User with email=%s was not found", email)));
return new Authority(user);
}
}
2. Сервер ресурсов (RESTful WS)
Конфигурация (большая часть кода скелета взята из ЭТО)
@Configuration
@EnableResourceServer
public class OAuth2ResourceConfig extends ResourceServerConfigurerAdapter{
@Autowired
DataSource dataSource;
String RESOURCE_ID = "data_resource";
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
TokenStore tokenStore = new JdbcTokenStore(dataSource);
resources
.resourceId(RESOURCE_ID)
.tokenStore(tokenStore);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
// For some reason we cant just "permitAll" OPTIONS requests which are needed for CORS support. Spring Security
// will respond with an HTTP 401 nonetheless.
// So we just put all other requests types under OAuth control and exclude OPTIONS.
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
.antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')")
.and()
// Add headers required for CORS requests.
.headers().addHeaderWriter((request, response) -> {
response.addHeader("Access-Control-Allow-Origin", "*");
if (request.getMethod().equals("OPTIONS")) {
response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Request-Method"));
response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
}
});
}
}
Контроллер WS:
@RestController
@RequestMapping(value = "/todos")
public class TodoController {
@Autowired
private TodoRepository todoRepository;
@RequestMapping(method = RequestMethod.GET)
public List<Todo> todos() {
return todoRepository.findAll();
}
// other methods
}