如何实现基本的Spring Security(会话管理)为单页AngularJS应用 [英] How to implement basic Spring security (session management) for Single Page AngularJS application

查看:1299
本文介绍了如何实现基本的Spring Security(会话管理)为单页AngularJS应用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am currently building a single page AngularJS application which communicates via REST to a backend. The structure is as follow:

一个Spring MVC的Web应用程序项目,它包含了所有AngularJS页面和资源,所有的REST控制器。

One Spring MVC WebApp project which contains all AngularJS pages and resources and all REST controllers.

一个真正的后端具有服务和存储库后端通信,一个API,如果你愿意。其余的电话会跟这些服务(第二个项目是作为第一位的依赖)。

A true backend which has services and repositories for backend communication, an API if you will. The REST calls will talk to these service (the second project is included as a dependency of the first one).

我一直在思考这个了很多,但我似乎无法找到任何可以帮助我。基本上我只需要在这个应用程序的一些安全性。我想一些会话管理这是非常简单的:

I have been thinking about this a lot but I can't seem to find anything that can help me. Basically I just need some security on this application. I'd like some kind of session management which is extremely simple:


  • 用户登录时,会话ID创建并存储在JS /饼干上
    网站

  • 当用户将重新加载页面/晚回来一个检查需要做,看是否会话ID仍然有效

  • 无来电应达到控制器如果会话ID无效

这是基本的会话managament的总体思路,这将是得到这个在Spring MVC Web应用程序实现的(没有JSP的,只是角度和REST控制器)最简单的方法。

This is the general idea of basic session managament, what would be the easiest way to get this implemented in a Spring MVC webapp (no JSP's, just angular and REST controllers).

在此先感谢!

推荐答案

您已经对REST API 2个选项:状态或无状态。

You have 2 options for the rest API: stateful or stateless.

第一种选择:HTTP会话认证 - 经典Spring Security认证机制。如果您计划扩展您在多个服务器上的应用程序,你需要让每个用户停留在同一台服务器上有粘性会话负载平衡器(或使用Redis的与春季会议)。

1st option: HTTP session authentication - the "classical" Spring Security authentication mechanism. If you plan to scale your application on multiple servers, you need to have a load balancer with sticky sessions so that each user stays on the same server (or use Spring Session with Redis).

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

2nd option: you have the choice of OAuth or token-based authentication.

的OAuth2是一个无状态的安全机制,所以如果你想在多台计算机扩展您的应用程序可能preFER它。 Spring Security提供OAuth2用户实现。与OAuth2用户最大的问题是需要有以存储其安全令牌多个数据库表。

OAuth2 is a stateless security mechanism, so you might prefer it if you want to scale your application across several machines. Spring Security provides an OAuth2 implementation. The biggest issue with OAuth2 is that requires to have several database tables in order to store its security tokens.

基于令牌的认证,如OAuth2用户,是一个无状态的安全机制,所以如果你想扩展在几个不同的服务器上它的另一个很好的选择。这种认证机制默认情况下不使用Spring Security存在。它更容易使用和实施比的OAuth2,因为它并不需要一个持久性机制,所以它适用于所有的SQL和NoSQL选项。该解决方案使用自定义的标记,这是你的用户名的MD5哈希值,令牌的有效期,密码和密钥。这样可以确保,如果有人偷了你的道理,他不应该能够提取您的用户名和密码。

Token-based authentication, like OAuth2, is a stateless security mechanism, so it's another good option if you want to scale on several different servers. This authentication mechanism doesn't exist by default with Spring Security. It is easier to use and implement than OAuth2, as it does not require a persistence mechanism, so it works on all SQL and NoSQL options. This solution uses a custom token, which is a MD5 hash of your user name, the expiration date of the token, your password, and a secret key. This ensures that if someone steals your token, he should not be able to extract your username and password.

我建议你寻找到 JHipster 。它会为你生成与REST API使用Spring引导和使用AngularJS前端的Web应用程序框架。当生成应用程序框架,它会问你,我上述的3认证机制之间做出选择。您可以重复使用code,它JHipster将在Spring MVC应用程序产生。

I recommend you to look into JHipster. It will generate a web app skeleton for you with REST API using Spring Boot and the front end using AngularJS. When generating the application skeleton it will ask you to choose between the 3 authentication mechanisms that I described above. You can reuse the code that JHipster will generate in your Spring MVC application.

下面是JHipster生成的TokenProvider的例子​​:

Here is an example of TokenProvider generated by JHipster:

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:

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配置:

And the respective AngularJS configuration:

'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:

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:

Add authInterceptor to $httpProvider:

.config(function ($httpProvider) {

    $httpProvider.interceptors.push('authInterceptor');

})

希望这是有帮助的!

Hope this is helpful!

这从 SpringDeveloper频道视频可能是有用的太:的大单页的应用程序需要伟大的后端的。它谈论的一些最佳实践(包括会话管理)和演示工作code的例子。

This video from SpringDeveloper channel may be useful too: Great single page apps need great backends. It talks about some best practices (including session management) and demos working code examples.

这篇关于如何实现基本的Spring Security(会话管理)为单页AngularJS应用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆