복붙노트

[SPRING] 단일 페이지 AngularJS 응용 프로그램을위한 기본 스프링 보안 (세션 관리) 구현 방법

SPRING

단일 페이지 AngularJS 응용 프로그램을위한 기본 스프링 보안 (세션 관리) 구현 방법

현재 REST를 통해 백엔드와 통신하는 단일 페이지 AngularJS 응용 프로그램을 구축 중입니다. 구조는 다음과 같습니다.

모든 AngularJS 페이지와 리소스와 모든 REST 컨트롤러를 포함하는 하나의 Spring MVC 웹 애플리케이션 프로젝트.

백엔드 커뮤니케이션을위한 서비스와 저장소가있는 진정한 백엔드, API. REST 호출은이 서비스와 대화한다 (두 번째 프로젝트는 첫 번째 프로젝트의 종속성으로 포함된다).

나는 이것을 많이 생각했지만 나에게 도움이 될만한 것을 찾을 수없는 것 같다. 기본적으로이 응용 프로그램에 보안이 필요합니다. 매우 간단한 세션 관리를 원합니다.

이것은 기본 세션 관리에 대한 일반적인 개념입니다. Spring MVC 웹 애플리케이션 (JSP가없고 각도 및 REST 컨트롤러 만)에서 구현할 수있는 가장 쉬운 방법은 무엇입니까?

미리 감사드립니다!

해결법

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

    1.나머지 API에는 stateful 또는 stateless라는 두 가지 옵션이 있습니다.

    나머지 API에는 stateful 또는 stateless라는 두 가지 옵션이 있습니다.

    첫 번째 옵션 : HTTP 세션 인증 - "고전적인"스프링 보안 인증 메커니즘. 여러 서버에서 응용 프로그램을 확장하려는 경우 각 사용자가 동일한 서버에 있거나 (Redis와 함께 Spring 세션을 사용하도록) 고정 세션이있는로드 밸런서가 있어야합니다.

    두 번째 옵션 : OAuth 또는 토큰 기반 인증을 선택할 수 있습니다.

    OAuth2는 무국적 보안 메커니즘이므로 여러 시스템에 걸쳐 응용 프로그램을 확장하려는 경우 OAuth2를 선호 할 수 있습니다. Spring Security는 OAuth2 구현을 제공합니다. OAuth2의 가장 큰 문제는 보안 토큰을 저장하기 위해 여러 데이터베이스 테이블이 있어야한다는 것입니다.

    OAuth2와 같은 토큰 기반 인증은 상태 비 저장 보안 메커니즘이므로 여러 서버에서 확장하려는 경우 다른 옵션이 좋습니다. 이 인증 메커니즘은 Spring Security와 함께 기본적으로 존재하지 않습니다. 지속성 메커니즘을 필요로하지 않으므로 OAuth2보다 사용하고 구현하기가 쉽기 때문에 모든 SQL 및 NoSQL 옵션에서 작동합니다. 이 솔루션은 사용자 이름의 MD5 해시, 토큰의 만료 날짜, 암호 및 비밀 키인 사용자 정의 토큰을 사용합니다. 이렇게하면 누군가가 토큰을 훔칠 경우 사용자 이름과 암호를 추출 할 수 없게됩니다.

    JHipster를 살펴 보길 권한다. Spring Boot를 사용하는 REST API와 AngularJS를 사용하는 프론트 엔드를 사용하여 웹 애플리케이션 스켈레톤을 생성합니다. 응용 프로그램 뼈대를 생성 할 때 위에 설명 된 3 가지 인증 메커니즘 중에서 선택하라는 메시지가 표시됩니다. Spring MVC 애플리케이션에서 JHipster가 생성 할 코드를 재사용 할 수 있습니다.

    다음은 JHipster에 의해 생성 된 TokenProvider의 예입니다.

    public class TokenProvider {
    
        private final String secretKey;
        private final int tokenValidity;
    
        public TokenProvider(String secretKey, int tokenValidity) {
            this.secretKey = secretKey;
            this.tokenValidity = tokenValidity;
        }
    
        public Token createToken(UserDetails userDetails) {
            long expires = System.currentTimeMillis() + 1000L * tokenValidity;
            String token = userDetails.getUsername() + ":" + expires + ":" + computeSignature(userDetails, expires);
            return new Token(token, expires);
        }
    
        public String computeSignature(UserDetails userDetails, long expires) {
            StringBuilder signatureBuilder = new StringBuilder();
            signatureBuilder.append(userDetails.getUsername()).append(":");
            signatureBuilder.append(expires).append(":");
            signatureBuilder.append(userDetails.getPassword()).append(":");
            signatureBuilder.append(secretKey);
    
            MessageDigest digest;
            try {
                digest = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException("No MD5 algorithm available!");
            }
            return new String(Hex.encode(digest.digest(signatureBuilder.toString().getBytes())));
        }
    
        public String getUserNameFromToken(String authToken) {
            if (null == authToken) {
                return null;
            }
            String[] parts = authToken.split(":");
            return parts[0];
        }
    
        public boolean validateToken(String authToken, UserDetails userDetails) {
            String[] parts = authToken.split(":");
            long expires = Long.parseLong(parts[1]);
            String signature = parts[2];
            String signatureToMatch = computeSignature(userDetails, expires);
            return expires >= System.currentTimeMillis() && signature.equals(signatureToMatch);
        }
    }
    

    SecurityConfiguration :

    @Configuration
    @EnableWebSecurity
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        @Inject
        private Http401UnauthorizedEntryPoint authenticationEntryPoint;
    
        @Inject
        private UserDetailsService userDetailsService;
    
        @Inject
        private TokenProvider tokenProvider;
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Inject
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth
                .userDetailsService(userDetailsService)
                    .passwordEncoder(passwordEncoder());
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring()
                .antMatchers("/scripts/**/*.{js,html}");
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)
            .and()
                .csrf()
                .disable()
                .headers()
                .frameOptions()
                .disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
                .authorizeRequests()
                    .antMatchers("/api/register").permitAll()
                    .antMatchers("/api/activate").permitAll()
                    .antMatchers("/api/authenticate").permitAll()
                    .antMatchers("/protected/**").authenticated()
            .and()
                .apply(securityConfigurerAdapter());
    
        }
    
        @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
        private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
        }
    
        private XAuthTokenConfigurer securityConfigurerAdapter() {
          return new XAuthTokenConfigurer(userDetailsService, tokenProvider);
        }
    
        /**
         * This allows SpEL support in Spring Data JPA @Query definitions.
         *
         * See https://spring.io/blog/2014/07/15/spel-support-in-spring-data-jpa-query-definitions
         */
        @Bean
        EvaluationContextExtension securityExtension() {
            return new EvaluationContextExtensionSupport() {
                @Override
                public String getExtensionId() {
                    return "security";
                }
    
                @Override
                public SecurityExpressionRoot getRootObject() {
                    return new SecurityExpressionRoot(SecurityContextHolder.getContext().getAuthentication()) {};
                }
            };
        }
    
    }
    

    AngularJS 구성은 다음과 같습니다.

    'use strict';
    
    angular.module('jhipsterApp')
        .factory('AuthServerProvider', function loginService($http, localStorageService, Base64) {
            return {
                login: function(credentials) {
                    var data = "username=" + credentials.username + "&password="
                        + credentials.password;
                    return $http.post('api/authenticate', data, {
                        headers: {
                            "Content-Type": "application/x-www-form-urlencoded",
                            "Accept": "application/json"
                        }
                    }).success(function (response) {
                        localStorageService.set('token', response);
                        return response;
                    });
                },
                logout: function() {
                    //Stateless API : No server logout
                    localStorageService.clearAll();
                },
                getToken: function () {
                    return localStorageService.get('token');
                },
                hasValidToken: function () {
                    var token = this.getToken();
                    return token && token.expires && token.expires > new Date().getTime();
                }
            };
        });
    

    authInceptceptor :

    .factory('authInterceptor', function ($rootScope, $q, $location, localStorageService) {
        return {
            // Add authorization token to headers
            request: function (config) {
                config.headers = config.headers || {};
                var token = localStorageService.get('token');
    
                if (token && token.expires && token.expires > new Date().getTime()) {
                  config.headers['x-auth-token'] = token.token;
                }
    
                return config;
            }
        };
    })
    

    $ httpProvider에 authInceptceptor를 추가하십시오.

    .config(function ($httpProvider) {
    
        $httpProvider.interceptors.push('authInterceptor');
    
    })
    

    희망이 도움이됩니다!

    SpringDeveloper 채널의이 비디오는 유용 할 수 있습니다. 훌륭한 단일 페이지 앱은 훌륭한 백엔드가 필요합니다. 세션 관리를 포함한 몇 가지 모범 사례와 데모 코드 작업 예제에 대해 설명합니다.

  2. from https://stackoverflow.com/questions/30126754/how-to-implement-basic-spring-security-session-management-for-single-page-angu by cc-by-sa and MIT license