복붙노트

[SPRING] Spring OAuth2.0 : 클라이언트 ID를 기반으로 사용자 역할 가져 오기

SPRING

Spring OAuth2.0 : 클라이언트 ID를 기반으로 사용자 역할 가져 오기

내 oauth2 인증 서버에 등록 된 클라이언트가 여러 개 있습니다. user1이 ROLE_A, ROLE_B와 같은 역할을 가지고 있다고 가정 해 봅시다. 동일한 사용자는 ROLE_C, ROLE_D와 같은 ROLL을 가지고 있습니다. 이제 사용자가 client1 또는 client2를 사용하여 로그인 할 때 그는 4 가지 역할 모두를 볼 수 있습니다. ROLE_A, ROLE_B, ROLE_C 및 ROLE_D.

내 요구 사항은 user1이 client1에 로그인 할 때 ROLE_A 및 ROLE_B 역할 만 리턴해야한다는 것입니다. client2를 사용하여 로그인하면 ROLE_C 및 ROLE_D 만 반환해야합니다.

이를 달성하기 위해, 내가 계획 한 것은 authenticate 함수 내에 있으며, 나는 clientId를 얻을 필요가있다. 그래서 clientId와 username을 사용하여 db (client-user-roles-mapping 테이블)에서 사용자에게 할당 된 해당 역할을 찾을 수 있습니다. 하지만 문제는 authenticate 함수 내에서 clientId를 얻는 방법을 모른다는 것입니다.

 @Override
    public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
        String userName = ((String) authentication.getPrincipal()).toLowerCase();
        String password = (String) authentication.getCredentials();
        if (userName != null && authentication.getCredentials() != null) {
                String clientId = // HERE HOW TO GET THE CLIENT ID 
                Set<String> userRoles = authRepository.getUserRoleDetails(userName.toLowerCase(), clientId);
                Collection<SimpleGrantedAuthority> authorities = fillUserAuthorities(userRoles);
                Authentication token =  new UsernamePasswordAuthenticationToken(userName, StringUtils.EMPTY, authorities);
                return token;
            } else {
                throw new BadCredentialsException("Authentication Failed!!!");
            }
         } else {
             throw new BadCredentialsException("Username or Password cannot be empty!!!");
         }         
    }

아무도 이것에 나를 도와주세요 수 없습니다.

업데이트 1

CustomAuthenticationProvider.java

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    private final Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private LDAPAuthenticationProvider ldapAuthentication;

    @Autowired
    private AuthRepository authRepository;

    public CustomAuthenticationProvider() {
        super();
    }

    @Override
        public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
            String userName = ((String) authentication.getPrincipal()).toLowerCase();
            String password = (String) authentication.getCredentials();
            if (userName != null && authentication.getCredentials() != null) {
                    String clientId = // HERE HOW TO GET THE CLIENT ID 
                    Set<String> userRoles = authRepository.getUserRoleDetails(userName.toLowerCase(), clientId);
                    Collection<SimpleGrantedAuthority> authorities = fillUserAuthorities(userRoles);
                    Authentication token =  new UsernamePasswordAuthenticationToken(userName, StringUtils.EMPTY, authorities);
                    return token;
                } else {
                    throw new BadCredentialsException("Authentication Failed!!!");
                }
             } else {
                 throw new BadCredentialsException("Username or Password cannot be empty!!!");
             }         
    }


    public boolean invokeAuthentication(String username, String password, Boolean isClientValidation) {
        try {
            Map<String, Object> userDetails = ldapAuthentication.authenticateUser(username, password);
            if(Boolean.parseBoolean(userDetails.get("success").toString())) {
                return true;
            }
        } catch (Exception exception) {
            log.error("Exception in invokeAuthentication::: " + exception.getMessage());
        }
        return false;
    }

    @Override
    public boolean supports(Class<? extends Object> authentication) {
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    }

    private Collection<SimpleGrantedAuthority> fillUserAuthorities(Set<String> roles) {
        Collection<SimpleGrantedAuthority> authorties = new ArrayList<SimpleGrantedAuthority>();
        for(String role : roles) {
            authorties.add(new SimpleGrantedAuthority(role));
        }
        return authorties;
    }
}

해결법

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

    1.수정 후 코드는 다음과 같습니다.

    수정 후 코드는 다음과 같습니다.

    @Override
    public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
        String userName = ((String) authentication.getPrincipal()).toLowerCase();
        String password = (String) authentication.getCredentials();
        if (userName != null && authentication.getCredentials() != null) {
                String clientId = getClientId();
                // validate client ID before use
                Set<String> userRoles = authRepository.getUserRoleDetails(userName.toLowerCase(), clientId);
                Collection<SimpleGrantedAuthority> authorities = fillUserAuthorities(userRoles);
                Authentication token =  new UsernamePasswordAuthenticationToken(userName, StringUtils.EMPTY, authorities);
                return token;
            } else {
                throw new BadCredentialsException("Authentication Failed!!!");
            }
         } else {
             throw new BadCredentialsException("Username or Password cannot be empty!!!");
         }         
    
    
    private  String getClientId(){
        final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    
        final String authorizationHeaderValue = request.getHeader("Authorization");
        final String base64AuthorizationHeader = Optional.ofNullable(authorizationHeaderValue)
                .map(headerValue->headerValue.substring("Basic ".length())).orElse("");
    
        if(StringUtils.isNotEmpty(base64AuthorizationHeader)){
            String decodedAuthorizationHeader = new String(Base64.getDecoder().decode(base64AuthorizationHeader), Charset.forName("UTF-8"));
            return decodedAuthorizationHeader.split(":")[0];
        }
    
        return "";
    }
    

    RequestContextHolder에 대한 추가 정보

  2. ==============================

    2.POJO는 사용자 이름과 암호뿐만 아니라 클라이언트 식별자도 보유해야합니다.

    POJO는 사용자 이름과 암호뿐만 아니라 클라이언트 식별자도 보유해야합니다.

    public ExtendedUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
      private final String clientId;
    
      public ExtendedUsernamePasswordAuthenticationToken(Object principal
                                                        , Object credentials
                                                        , String clientId) {
        super(principal, credentials);
    
        this.clientId = clientId;
      }
    
      public String getClientId() { return clientId; }
    }
    

    클라이언트 식별자가 사용자 이름과 암호 외에도 인증 코드로 전달되도록 인증 프로세스를 조정해야합니다.

    public class ExtendedUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
      public ExtendedUsernamePasswordAuthenticationFilter () { super(); }
    
      @Override
      public public Authentication attemptAuthentication(HttpServletRequest request
                                                        , HttpServletResponse response)
                                                        throws AuthenticationException {
        // See the source code of UsernamePasswordAuthenticationFilter
        // to implement this. Instead of creating an instance of
        // UsernamePasswordAuthenticationToken, create an instance of
        // ExtendedUsernamePasswordAuthenticationToken, something along
        // the lines of:
    
        final String username = obtainUsername(request);
        final String password = obtainPassword(request);
        final String clientId = obtainClientId(request);
    
        ...
    
        final Authentication authentication = new ExtendedUsernamePasswordAuthenticationToken(username, password, clientId);
    
        return getAuthenticationManager().authenticate(authentication);
      }
    }
    
    public CustomAuthenticationProvider implements AuthenticationProvider {
      ...
    
      @Override
      public boolean supports(final Class<?> authentication) {
        return authentication.isAssignableFrom(ExtendedUsernamePasswordAuthenticationToken.class);
      }
    
    
      @Override
      public Authentication authenticate(final Authentication authentication)
                                         throws AuthenticationException {
      }
    }
    
    <bean class="com.path.to.filter.ExtendedUsernamePasswordAuthenticationFilter" id="formAuthenticationFilter">
      <property name="authenticationManager" ref="authenticationManager"/>
    </bean>
    
    <http ... >
      <security:custom-filter position="FORM_LOGIN_FILTER" ref="formAuthenticationFilter"/>
    
      ...
    </http>
    

    또는 Java 구성을 사용하는 경우 :

    @Bean
    public ExtendedUsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter(final AuthenticationManager authenticationManager) {
      final ExtendedUsernamePasswordAuthenticationFilter filter = new ExtendedUsernamePasswordAuthenticationFilter();
    
      filter.setAuthenticationManager(authenticationManager);
    
      return filter;
    }
    
    protected void configure(HttpSecurity http) throws Exception {
      http.addFilterAt(usernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
          ...
    }
    
  3. ==============================

    3.요청에서 추가 매개 변수에 액세스하기를 원하기 때문에 CustomAuthenticationProvider 클래스에서 다음을 시도해 볼 수 있습니다

    요청에서 추가 매개 변수에 액세스하기를 원하기 때문에 CustomAuthenticationProvider 클래스에서 다음을 시도해 볼 수 있습니다

    @Autowired
        private HttpServletRequest request;
    

    다음 논리를 추가하여 http 요청 매개 변수를 읽고 논리를 추가하여 인증 키에 액세스하십시오.

    @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    
        Enumeration<String> headerNames = request.getHeaderNames();
        while(headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            System.out.println("Header Name - " + headerName + ", Value - " + request.getHeader(headerName));
       }
    }
    

    이제 다음과 같이 디코딩 할 수있는 기본 인증 필드 인코딩을 갖게됩니다.

    if (authorization != null && authorization.startsWith("Basic")) {
            // Authorization: Basic base64credentials
            String base64Credentials = authorization.substring("Basic".length()).trim();
            String credentials = new String(Base64.getDecoder().decode(base64Credentials),
                    Charset.forName("UTF-8"));
            // client/secret = clientId:secret
            final String[] values = credentials.split(":",2);
    
  4. from https://stackoverflow.com/questions/49229551/spring-oauth2-0-getting-user-roles-based-on-client-id by cc-by-sa and MIT license