有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java如何为单页AngularJS应用程序实现基本的Spring安全性(会话管理)

我目前正在构建一个单页AngularJS应用程序,它通过REST与后端进行通信。结构如下:

一个Spring MVC WebApp项目,包含所有AngularJS页面和资源以及所有REST控制器

一个真正的后端,它有用于后端通信的服务和存储库,如果您愿意,它是一个API。其余调用将与这些服务进行通信(第二个项目作为第一个项目的依赖项包含)

我一直在思考这个问题,但我似乎找不到任何可以帮助我的东西。基本上我只需要这个应用程序的一些安全性。我想要一种非常简单的会话管理:

  • 用户登录,会话id被创建并存储在 网站
  • 当用户稍后重新加载页面/返回时,需要进行检查以查看会话id是否仍然有效
  • 如果会话id无效,则控制器不应收到任何调用

这是基本会话管理的一般思想,在SpringMVCwebapp中实现它的最简单方法是什么(没有JSP,只有angular和REST控制器)

提前谢谢


共 (2) 个答案

  1. # 1 楼答案

    从JHipsterhttps://jhipster.github.io/中所做的事情来看。你甚至可以使用它

    Jhipster是一款弹簧靴+角度/角度发电机。我经常用它来激励自己,学习最佳实践

  2. # 2 楼答案

    RESTAPI有两个选项:有状态或无状态

    第一个选项:HTTP会话身份验证——“经典”Spring安全身份验证机制。如果计划在多台服务器上扩展应用程序,则需要一个具有粘性会话的负载平衡器,以便每个用户都留在同一台服务器上(或者使用带Redis的Spring会话)

    第二个选项:您可以选择OAuth或基于令牌的身份验证

    OAuth2是一种无状态安全机制,因此,如果希望跨多台机器扩展应用程序,您可能更喜欢它。Spring安全性提供了OAuth2实现。OAuth2最大的问题是需要有几个数据库表才能存储其安全令牌

    基于令牌的身份验证,如OAuth2,是一种无状态安全机制,因此,如果您想在多个不同的服务器上扩展,它是另一个很好的选择。Spring Security默认情况下不存在此身份验证机制。它比OAuth2更易于使用和实现,因为它不需要持久性机制,所以可以在所有SQL和NoSQL选项上工作。此解决方案使用自定义令牌,它是用户名、令牌过期日期、密码和密钥的MD5哈希。这确保了如果有人偷了您的令牌,他将无法提取您的用户名和密码

    我建议你调查一下。它将通过使用Spring Boot的RESTAPI和使用AngularJS的前端为您生成web应用程序框架。在生成应用程序框架时,它将要求您在我上面描述的3种身份验证机制中进行选择。您可以重用JHipster将在SpringMVC应用程序中生成的代码

    以下是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);
        }
    }
    

    证券配置:

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

    authInterceptor:

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

    将authInterceptor添加到$httpProvider:

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

    希望这是有帮助的

    来自SpringDeveloper channel的这段视频可能也很有用:Great single page apps need great backends。它讨论了一些最佳实践(包括会话管理)和演示工作代码示例