복붙노트

[SPRING] 페이스 북 토큰을 인증에 사용하는 Stateless REST Endpoint 용 스프링 소셜 인증 필터

SPRING

페이스 북 토큰을 인증에 사용하는 Stateless REST Endpoint 용 스프링 소셜 인증 필터

Spring Security를 ​​사용하여 REST 백엔드 인증에 Facebook Token을 사용하고 싶습니다. 이 보안을 Spring Application에 어떻게 통합 할 수 있는지 자세히 설명해 주시겠습니까?

Spring Social Security와 동일한 사용자 관리를 사용하고 싶습니다. UserConnection 테이블 및 로컬 사용자 테이블

해결법

  1. ==============================

    1.코드 샘플은 다음 위치에서 다운로드 할 수 있습니다.

    코드 샘플은 다음 위치에서 다운로드 할 수 있습니다.

    https://github.com/ozgengunay/FBSpringSocialRESTAuth

    우리는 REST 클라이언트가 이미 가지고있는 Facebook OAuth Token을 사용하여 REST 백엔드를 보호하는 "Spring"솔루션을 찾고있었습니다. 예 : Facebook Connect SDK가 포함 된 모바일 앱이 앱 자체에 구현되어 있고 다른 한편으로는 REST API를 제공하는 백엔드가 있습니다. Facebook OAuth Token으로 REST API 호출을 인증하려고합니다. 이 솔루션은이 시나리오를 실현합니다.

    불행히도 Spring Social Security Framework는 상태없는 HTTP 요청을 보호하기 만하며, 여러분의 stateless REST 백엔드는 보호하지 않습니다.

    이것은 하나의 구성 요소 인 FacebookTokenAuthenticationFilter로 구성된 봄 사회 보장 프레임 워크의 확장입니다. 이 필터는 모든 REST 호출을 차단합니다. 클라이언트는 REST API가 무적이기 때문에 모든 요청에서 url의 Facebook OAuth Token을 "input_token"매개 변수로 보내야합니다. 필터는이 토큰을 찾고 "debug_token"Graph API 호출로 유효성을 검사합니다. 토큰의 유효성이 검사되면 필터는 사용자를 로컬 사용자 관리 시스템과 일치 시키려고 시도합니다. 그러한 사용자가 아직 등록되지 않은 경우, 필터는 사용자를 새로운 사용자로 등록합니다.

    웹 백엔드처럼 REST API 이외의 서비스가있는 경우 Spring Social Security의 표준 SocialAuthenticationFilter와 함께이 필터를 사용할 수 있습니다. 따라서 동일한 사용자 관리 시스템을 사용할 수 있습니다.

    1) MYSQL에서 다음과 같이 사용자 테이블을 만듭니다.

    CREATE TABLE IF NOT EXISTS `user` (
      `id` varchar(50) NOT NULL,
      `email` varchar(255) NOT NULL COMMENT 'unique',
      `first_name` varchar(255) NOT NULL,
      `last_name` varchar(255) NOT NULL,
      `password` varchar(255) DEFAULT NULL,
      `role` varchar(255) NOT NULL,
      `sign_in_provider` varchar(20) DEFAULT NULL,
      `creation_time` datetime NOT NULL,
      `modification_time` datetime NOT NULL,
      `status` varchar(20) NOT NULL COMMENT 'not used',
      PRIMARY KEY (`id`),
      UNIQUE KEY `email` (`email`)
    );
    

    2) context.xml에서 데이터 소스를 구성하십시오.

    바람둥이의 context.xml :

    <Resource auth="Container" driverClassName="com.mysql.jdbc.Driver" maxActive="100" maxIdle="30" maxWait="10000" 
    name="jdbc/thingabled" password="..." type="javax.sql.DataSource" url="jdbc:mysql://localhost:3306/..." username="..."/>
    

    3) Spring 설정 : 인증을 위해 FacebookTokenAuthenticationFilter에 의해 "protected"로 시작하는 URL을 차단하도록 스프링 보안을 설정합니다. 권한 부여는 "ROLE_USER_REST_MOBILE"역할에 의해 수행됩니다.

    <security:http use-expressions="true" pattern="/protected/**"
    create-session="never" entry-point-ref="forbiddenEntryPoint">
      <security:intercept-url pattern="/**"
      access="hasRole('ROLE_USER_REST_MOBILE')" />
    <!-- Adds social authentication filter to the Spring Security filter chain. -->
      <security:custom-filter ref="facebookTokenAuthenticationFilter"
      before="FORM_LOGIN_FILTER" />
    </security:http>
    
    
    <bean id="facebookTokenAuthenticationFilter"
    class="com.ozgen.server.security.oauth.FacebookTokenAuthenticationFilter">
      <constructor-arg index="0" ref="authenticationManager" />
      <constructor-arg index="1" ref="userIdSource" />
      <constructor-arg index="2" ref="usersConnectionRepository" />
      <constructor-arg index="3" ref="connectionFactoryLocator" />
    </bean>
    
    <security:authentication-manager alias="authenticationManager">
      <security:authentication-provider
      ref="socialAuthenticationProvider" />
    </security:authentication-manager>
    
    <!-- Configures the social authentication provider which processes authentication 
    requests made by using social authentication service (FB). -->
    <bean id="socialAuthenticationProvider"
    class="org.springframework.social.security.SocialAuthenticationProvider">
      <constructor-arg index="0" ref="usersConnectionRepository" />
      <constructor-arg index="1" ref="simpleSocialUserDetailsService" />
    </bean>
    
    <bean id="forbiddenEntryPoint"
    class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />
    
    <!-- This bean determines the account ID of the user.-->
    <bean id="userIdSource"
    class="org.springframework.social.security.AuthenticationNameUserIdSource" />
    
    <!-- This is used to hash the password of the user. -->
    <bean id="passwordEncoder"
    class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
      <constructor-arg index="0" value="10" />
    </bean>
    <!-- This bean encrypts the authorization details of the connection. In 
    our example, the authorization details are stored as plain text. DO NOT USE 
    THIS IN PRODUCTION. -->
    <bean id="textEncryptor" class="org.springframework.security.crypto.encrypt.Encryptors"
    factory-method="noOpText" />
    

    4) 모든 Stateless REST 요청은 유효한 Facebook Token을 사용하여 요청을 인증하기 위해 FacebookTokenAuthenticationFilter에 의해 차단됩니다. Facebook 토큰이 유효한지 확인합니다. Facebook 토큰이 유효하지 않으면 요청이 거부됩니다. Facebook 토큰이 유효하지 않으면 필터는 SimpleSocialUserDetailsService를 통해 요청을 인증하려고 시도합니다. 사용자 및 사용자 연결 데이터를 사용할 수없는 경우 새 사용자 (UserService를 통해) 및 UserConnection이 만들어집니다.

    private Authentication attemptAuthService(...) {
      if (request.getParameter("input_token") == null) {
        throw new SocialAuthenticationException("No token in the request");
      }
      URIBuilder builder = URIBuilder.fromUri(String.format("%s/debug_token", "https://graph.facebook.com"));
      builder.queryParam("access_token", access_token);
      builder.queryParam("input_token", request.getParameter("input_token"));
      URI uri = builder.build();
      RestTemplate restTemplate = new RestTemplate();
    
      JsonNode resp = null;
      try {
        resp = restTemplate.getForObject(uri, JsonNode.class);
      } catch (HttpClientErrorException e) {
        throw new SocialAuthenticationException("Error validating token");
      }
      Boolean isValid = resp.path("data").findValue("is_valid").asBoolean();
      if (!isValid)
        throw new SocialAuthenticationException("Token is not valid");
    
      AccessGrant accessGrant = new AccessGrant(request.getParameter("input_token"), null, null,
        resp.path("data").findValue("expires_at").longValue());
    
      Connection<?> connection = ((OAuth2ConnectionFactory<?>) authService.getConnectionFactory())
        .createConnection(accessGrant);
      SocialAuthenticationToken token = new SocialAuthenticationToken(connection, null);
      Assert.notNull(token.getConnection());
    
      Authentication auth = SecurityContextHolder.getContext().getAuthentication();
      if (auth == null || !auth.isAuthenticated()) {
        return doAuthentication(authService, request, token);
      } else {
        addConnection(authService, request, token);
        return null;
      }
     }
    

    5) 프로젝트의 다른 중요한 섹션 :

    사용자 : '사용자'테이블을 매핑하는 엔터티.

    @Entity
    @Table(name = "user")
    public class User extends BaseEntity {
    
      @Column(name = "email", length = 255, nullable = false, unique = true)
      private String email;
    
      @Column(name = "first_name", length = 255, nullable = false)
      private String firstName;
    
      @Column(name = "last_name", length = 255, nullable = false)
      private String lastName;
    
      @Column(name = "password", length = 255)
      private String password;
    
      @Column(name = "role", length = 255, nullable = false)
      private String rolesString;
    
      @Enumerated(EnumType.STRING)
      @Column(name = "sign_in_provider", length = 20)
      private SocialMediaService signInProvider;
    
      ...
    }
    

    UserRepository : 'User'엔티티에서 CRUD 연산을 수행 할 수있는 Spring Data JPA 저장소.

    public interface UserRepository extends JpaRepository<User, String> {
      public User findByEmailAndStatus(String email,Status status);
      public User findByIdAndStatus(String id,Status status);
    }
    

    UserService :이 스프링 서비스는 'user'테이블에 데이터를 삽입하는 새로운 사용자 계정을 만드는 데 사용됩니다.

    @Service
    public class UserService {
      private static final Logger LOGGER = LoggerFactory.getLogger(UserService.class);
    
      @Autowired
      private UserRepository repository;
    
      @Transactional
      public User registerNewUserAccount(RegistrationForm userAccountData) throws DuplicateEmailException {
          LOGGER.debug("Registering new user account with information: {}", userAccountData);
    
          if (emailExist(userAccountData.getEmail())) {
              LOGGER.debug("Email: {} exists. Throwing exception.", userAccountData.getEmail());
              throw new DuplicateEmailException("The email address: " + userAccountData.getEmail() + " is already in use.");
          }
    
          LOGGER.debug("Email: {} does not exist. Continuing registration.", userAccountData.getEmail());
    
          User registered =User.newEntity();
          registered.setEmail(userAccountData.getEmail());
          registered.setFirstName(userAccountData.getFirstName());
          registered.setLastName(userAccountData.getLastName());
          registered.setPassword(null);
          registered.addRole(User.Role.ROLE_USER_WEB);
          registered.addRole(User.Role.ROLE_USER_REST);
          registered.addRole(User.Role.ROLE_USER_REST_MOBILE);
    
          if (userAccountData.isSocialSignIn()) {
              registered.setSignInProvider(userAccountData.getSignInProvider());
          }
    
          LOGGER.debug("Persisting new user with information: {}", registered);
    
          return repository.save(registered);
      }
      .... 
    }
    

    SimpleSocialUserDetailsService :이 스프링 서비스는 SocialAuthenticationProvider가 사용자의 userId를 인증하는 데 사용됩니다.

    @Service
    public class SimpleSocialUserDetailsService implements SocialUserDetailsService {
      private static final Logger LOGGER = LoggerFactory.getLogger(SimpleSocialUserDetailsService.class);
      @Autowired
      private UserRepository repository;
    
      @Override
      public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException, DataAccessException {
          LOGGER.debug("Loading user by user id: {}", userId);
    
          User user = repository.findByEmailAndStatus(userId, Status.ENABLED);
          LOGGER.debug("Found user: {}", user);
    
          if (user == null) {
              throw new UsernameNotFoundException("No user found with username: " + userId);
          }
    
          ThingabledUserDetails principal = new ThingabledUserDetails(user.getEmail(),user.getPassword(),user.getAuthorities());
          principal.setFirstName(user.getFirstName());
          principal.setId(user.getId());
          principal.setLastName(user.getLastName());
          principal.setSocialSignInProvider(user.getSignInProvider());
    
    
          LOGGER.debug("Found user details: {}", principal);
    
          return principal;
      }
    } 
    

    코드 샘플은 다음 위치에서 다운로드 할 수 있습니다.

    https://github.com/ozgengunay/FBSpringSocialRESTAuth

  2. from https://stackoverflow.com/questions/35911723/spring-social-authentication-filter-for-stateless-rest-endpoints-which-use-faceb by cc-by-sa and MIT license