简体   繁体   English

使用Java和Angular2登录Spring Security

[英]Spring Security login with Java and Angular2

I am having some trouble integrating spring security (namely the login part) in my web application. 我在我的Web应用程序中集成spring security(即登录部分)时遇到了一些麻烦。 My BackEnd is running on localhost:8080 and the FrontEnd (Ionic 2 with AngularJS 2) is running on localhost:8100 . 我的BackEnd在localhost:8080上运行,FrontEnd(Ionic 2与AngularJS 2)在localhost:8100上运行。 I managed to log in (at least I think so) but the rest of the requests become unavailable and I get the following error in the Chrome Developer Console: 我设法登录(至少我是这么认为),但其他请求变得不可用,我在Chrome开发者控制台中收到以下错误:

XMLHttpRequest cannot load http://localhost:8080/company/getAll. Response for preflight is invalid (redirect)

When testing with Postman it seems to be working, I log in and then I am able to perform POST requests on http://localhost:8080/company/getAll and everything works as intended. 当使用Postman进行测试时,它似乎正在工作,我登录然后我能够在http:// localhost:8080 / company / getAll上执行POST请求,一切都按预期工作。

Presumably I'm missing something (like a token) but can't figure out what. 据推测,我错过了某些东西(比如令牌)但却无法弄清楚是什么。 I am new to both Ionic and Spring Security so please forgive me if this is something trivial. 我是Ionic和Spring Security的新手,所以请原谅我,如果这是微不足道的话。 I tried googling various tutorials but was unable to find anything (most of them were using JSP). 我尝试使用谷歌搜索各种教程,但无法找到任何东西(大多数都使用JSP)。

How could I get this to work? 我怎么能让这个工作? How should my requests from the frontend look like? 我的前端请求应该怎么样?

Here is my login method from the BackEnd controller: 这是我从BackEnd控制器登录的方法:

@RequestMapping(value = "/login", method = RequestMethod.GET)
    @CrossOrigin(origins = "*")
    public ResponseEntity<GeneralResponse> login(Model model, String error, String logout) {
        System.out.println("LOGIN"+model.toString());

        if (error != null) {
            System.out.println("1.IF");
            model.addAttribute("error", "Your username and password is invalid.");
        }

        if (logout != null) {
            System.out.println("2.IF");
            model.addAttribute("message", "You have been logged out successfully.");
        }

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new GeneralResponse(true, "FAILURE: Error"));
    }

This is my WebSecurityConfig: 这是我的WebSecurityConfig:

@Configuration
@EnableWebSecurity
@ComponentScan("com.SAB.service")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/resources/**", "/registration").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/user/login")
            .permitAll()
            .and()
            .logout()
            .permitAll();
        http
            .csrf().disable();
        http.formLogin().defaultSuccessUrl("http://localhost:8100/", true);
        //.and().exceptionHandling().accessDeniedPage("/403")
        //.and().sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

And finally my FrontEnd code responsible for logging in: 最后我的FrontEnd代码负责登录:

  login(){
    var body = 'username='+this.loginForm.controls['username'].value+'&password='+this.loginForm.controls['password'].value;
    var headers = new Headers();
    headers.append('Content-Type', 'application/x-www-form-urlencoded');
    //headers.append("Access-Control-Allow-Origin", "*");

    this.http
      .post('http://localhost:8080/user/login',
        body, {
          headers: headers
        })
      .subscribe(data => {
        console.log('ok');
        console.log(data)
      }, error => {
        console.log(JSON.stringify(error.json()));
      });
  }

If you require any additional details please let me know and I will provide them. 如果您需要任何其他详细信息,请告诉我,我会提供。

Thanks in advance! 提前致谢!

I don't get the Access-COntroll-Origin error but I tried to change it according to the comments anyway and now I get a "Invalid CORS request" 我没有得到Access-COntroll-Origin错误,但我试图根据评论改变它,现在我收到“无效的CORS请求”

Request and Response Header for login from postman: 从邮递员登录的请求和响应标题: 在此输入图像描述

EDIT: Here are the Spring Security logs. 编辑:这是Spring Security日志。 However Spring only registers a OPTIONS request and no POST even though the FrontEnd is calling POST. 但是,即使FrontEnd正在调用POST,Spring也只注册OPTIONS请求而不注册POST。

************************************************************

Request received for OPTIONS '/login':

org.apache.catalina.connector.RequestFacade@18859793

servletPath:/login
pathInfo:null
headers: 
host: localhost:8080
connection: keep-alive
access-control-request-method: POST
origin: http://localhost:8100
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36
access-control-request-headers: access-control-allow-origin
accept: */*
referer: http://localhost:8100/
accept-encoding: gzip, deflate, sdch, br
accept-language: en-US,en;q=0.8


Security filter chain: [
  WebAsyncManagerIntegrationFilter
  SecurityContextPersistenceFilter
  HeaderWriterFilter
  CorsFilter
  LogoutFilter
  UsernamePasswordAuthenticationFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]


************************************************************
2017-05-12 19:18:28.527 DEBUG 16500 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2017-05-12 19:18:28.527 DEBUG 16500 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2017-05-12 19:18:28.527 DEBUG 16500 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2017-05-12 19:18:28.527 DEBUG 16500 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: null. A new one will be created.
2017-05-12 19:18:28.529 DEBUG 16500 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login at position 3 of 12 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2017-05-12 19:18:28.529 DEBUG 16500 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /login at position 4 of 12 in additional filter chain; firing Filter: 'CorsFilter'
2017-05-12 19:18:28.541 DEBUG 16500 --- [nio-8080-exec-1] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@5baaaa2b
2017-05-12 19:18:28.541 DEBUG 16500 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2017-05-12 19:18:28.541 DEBUG 16500 --- [nio-8080-exec-1] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed

UPDATE: So after adding the CORSFilter suggested in the accepted answer I also had to modify my requests in the frontend in order for the cookie with the JSESSIONID to be sent on each request. 更新:所以在添加了接受的答案中建议的CORSFilter之后,我还必须在前端修改我的请求,以便在每个请求上发送带有JSESSIONID的cookie。 Bassically I had to add the following to ALL my reqquests: let options = new RequestOptions({ headers: headers, withCredentials: true }); 我不得不将以下内容添加到我的所有请求中: let options = new RequestOptions({ headers: headers, withCredentials: true });

This seems to be problem related to CORS configuration. 这似乎是与CORS配置相关的问题。 Browser does make the OPTION preflight request before your actual GET call. 浏览器确实会在您的实际GET调用之前发出OPTION预检请求。 I don't have much knowledge in this area, but you could try adding the filter below to the spring back end. 我对这方面的知识不多,但您可以尝试将下面的过滤器添加到弹簧后端。

public class CORSFilter extends GenericFilterBean {

    public void doFilter(ServletRequest request, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse response = (HttpServletResponse) res;
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
        response.addHeader("Access-Control-Max-Age", "3600");
        response.addHeader("Access-Control-Allow-Headers", "X-Requested-With, X-Auth-Token");
        response.addHeader("Access-Control-Allow-Credentials", "true");

        if(req.getMethod().equalsIgnoreCase("options")){
             return;
        }
        chain.doFilter(request, response);
    }
}

And add the line below in the configure method of your WebSecurityConfig. 并在WebSecurityConfig的configure方法中添加以下行。

.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);

The solution is very simple. 解决方案非常简单。 The username and password on POST needs to be submitted as FormData and not part of body. POST上的用户名和密码需要作为FormData提交,而不是正文的一部分。 I had the same issue with my app and after a long struggle I created a HTML form and did a POST then it worked like a charm. 我的应用程序遇到了同样的问题,经过长时间的努力,我创建了一个HTML表单并进行了POST,然后它就像一个魅力。

See below live example. 见下面的实例。 You should be able to signup with any id and try to login and debut it in your browser. 您应该可以使用任何ID注册并尝试登录并在浏览器中首次亮相。 It uses Spring boot for server side and Angular 4 on UI. 它在服务器端使用Spring引导,在UI上使用Angular 4。

Live Demo: http://shop-venkatvp.rhcloud.com/#/ 现场演示: http //shop-venkatvp.rhcloud.com/#/

Login HTML: https://github.com/reflexdemon/shop/blob/master/src/app/login/login.component.html#L7-L31 登录HTML: https //github.com/reflexdemon/shop/blob/master/src/app/login/login.component.html#L7-L31

Spring Security Configuration: https://github.com/reflexdemon/shop/blob/master/src/main/java/org/shop/WebSecurityConfig.java#L20-L34 Spring安全配置: https //github.com/reflexdemon/shop/blob/master/src/main/java/org/shop/WebSecurityConfig.java#L20-L34

Login controller: https://github.com/reflexdemon/shop/blob/master/src/main/java/org/shop/page/controller/LoginController.java 登录控制器: https //github.com/reflexdemon/shop/blob/master/src/main/java/org/shop/page/controller/LoginController.java

Hope this is helpful. 希望这是有帮助的。

I'm in the middle of writing an Angular 4 and spring boot integration article. 我正在编写Angular 4和spring boot集成文章。 I don't have the article finished yet but the source code is done. 我还没有完成文章,但源代码已完成。 I'm not sure why you are trying to serve your application from two different hosts, this unnecessarily complicates your application by injecting a cors requirement and having to manage two security context's. 我不确定您为什么要尝试从两个不同的主机提供应用程序,这会通过注入cors要求并且必须管理两个安全上下文而不必要地使您的应用程序复杂化。 If this isn't an absolute requirement I would serve everything from the same server. 如果这不是绝对的要求,我会从同一台服务器上提供服务。

Take a look at this github project for a full integration of angular and spring boot: https://github.com/tschiman/tutorials/tree/master/spring-cloud/spring-cloud-bootstrap/gateway . 看一下这个github项目,完全集成角度和弹簧启动: https//github.com/tschiman/tutorials/tree/master/spring-cloud/spring-cloud-bootstrap/gateway You can ignore the zuul stuff in there if you don't need that. 如果你不需要,你可以忽略那里的zuul东西。 It has no impact on the support for the angular ui. 它对角度ui的支持没有影响。

If you want to enable csrf, and you should, look at this commit: be4b206478c136d24ea1bf8b6f93da0f3d86a6c3 如果你想启用csrf,你应该看看这个提交:be4b206478c136d24ea1bf8b6f93da0f3d86a6c3

If reading that isn't making sense these are the steps you need to follow: 如果阅读没有意义,这些是您需要遵循的步骤:

  1. Move your angular source code into your spring boot app 将角度源代码移动到spring boot应用程序中
  2. Modify you angular-cli.json file to have an output directory in your resources static or resources public folder: "outDir":"../../resources/static/home" This sets the output directory for when you run ng build from the command line using the angular cli. 修改angular-cli.json文件,使资源中的输出目录为static或resources公共文件夹: "outDir":"../../resources/static/home"这将设置运行构建时的输出目录从命令行使用angular cli。
  3. Modify your login's default success url to redirect to /home/index.html .formLogin().defaultSuccessUrl("/home/index.html", true) this will force a successful login to always redirect to your angular app. 修改登录的默认成功URL以重定向到/home/index.html .formLogin().defaultSuccessUrl("/home/index.html", true)这将强制成功登录以始终重定向到您的角度应用程序。
  4. If you want to automate building your angular app into maven then add this plugin to your POM file: 如果要将角度应用程序自动构建为maven,请将此插件添加到POM文件中:

      <plugin> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <phase>generate-resources</phase> <configuration> <tasks> <exec executable="cmd" osfamily="windows" dir="${project.basedir}/src/main/angular/ui"> <arg value="/c"/> <arg value="ng"/> <arg value="build"/> </exec> <exec executable="/bin/sh" osfamily="mac" dir="${project.basedir}/src/main/angular/ui"> <arg value="-c"/> <arg value="ng build"/> </exec> </tasks> </configuration> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> 

If you have additional questions I am happy to respond further if this was not enough for you to get it working. 如果您还有其他问题,我很乐意进一步回答,如果这还不足以让您开始工作。

Did you try to explicitly permit OPTIONS requests? 您是否尝试明确允许OPTIONS请求? Like this: 像这样:

protected void configure(HttpSecurity http) throws Exception {
   ...
   http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll();
   ...
}

First of all - this is definitely a CORS configuration issue because you are running backend and frontend on different addresses. 首先 - 这绝对是一个CORS配置问题,因为您在不同的地址上运行后端和前端。

You can twick backend to support CORS requests but this is a dirty solution, espesialy since you probably do not want CORS in production version of you app. 您可以使用后端来支持CORS请求,但这是一个肮脏的解决方案,尤其是因为您可能不希望在应用程序的生产版本中使用CORS Much cleaner approach would be to configure either frontend or backend to server as a proxy to the other. 更简洁的方法是将前端或后端配置为服务器作为另一个的代理。

I prefer to configure frontend to proxy requests to backend. 我更喜欢将前端配置为代理后端请求。 This way it will have no effect on production version of the app. 这样它对应用程序的生产版本没有任何影响。

Next part assumes you are using angular-cli (as you should with angular2 app). 下一部分假设您正在使用angular-cli (正如您应该使用angular2 app)。

They have a section in their docs specifically for proxy configuration: https://www.npmjs.com/package/angular-cli#proxy-to-backend . 他们的文档中有一节专门用于代理配置: https//www.npmjs.com/package/angular-cli#proxy-to-backend

I have all my REST services inside /api context, so my proxy.conf.json looks like this: 我在/ api上下文中有我所有的REST服务,所以我的proxy.conf.json看起来像这样:

{
  "/api": {
    "target": "http://localhost:8083",
    "secure": false
  },
  "/login": {
    "target": "http://localhost:8083",
    "secure": false
  },
  "/logout": {
    "target": "http://localhost:8083",
    "secure": false
  }
}

Where http://localhost:8083 is my backend. http:// localhost:8083是我的后端。 You can then run your app with: 然后,您可以运行以下应用:

ng serve --proxy-config proxy.conf.json

and use only your frontend address. 并仅使用您的前端地址。 No CORS configuration needed. 无需CORS配置。

PS If you dont use angular-cli Im sure you can use this approach with whatever tool you use to serve frontent. PS如果你不使用angular-cli我相信你可以使用这种方法用你用来提供前卫的任何工具。 Before switching to angular-cli I used this with lite-server as well. 在切换到angular-cli之前,我也使用了lite-server

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM