简体   繁体   English

如何在 Spring 安全后端正确验证来自 Angular 应用程序的用户

[英]How to correctly authenticate User from an Angular app at an Spring Security Backend

I am trying to authenticate my Angular 8 application at my Spring Boot 2.1 backend which is secured by Spring Security.我正在尝试在我的 Spring Boot 2.1 后端验证我的 Angular 8 应用程序,该后端由 Spring Security 保护。 The users request is checked against the Active Directory.根据 Active Directory 检查用户请求。 If the user can be authenticated, I want to return a JSON response containig username and authorities.如果用户可以通过身份验证,我想返回一个包含用户名和权限的 JSON 响应。 Adittionally i want to set a cookie.另外我想设置一个cookie。 I managed to get around the cors problem and I am able to get the correct response using Postman.我设法解决了 cors 问题,并且能够使用 Postman 获得正确的响应。 But I can not get the correct information in my Angular application.但我无法在我的 Angular 应用程序中获得正确的信息。

Spring Boot Spring 开机

LoginController.java登录控制器.java

@RestController
@CrossOrigin(origins = "http://localhost:4200", allowCredentials = "true")
@RequestMapping("/login")
public class LoginController {
    SecurityContext context;
    Authentication authentication;
    Collection<GrantedAuthority> grantedAuthorities;
    String cips_authorities = "";

    @GetMapping(value = "/loginPage")
    public String loginPage() {
        String loginForm = "<html >\n " +
        "<head></head>\n" +
                "<body>\n" +
                "   <h1>Login</h1>\n" +
                "   <form name='f' action=\"/login/loginPage\" method='post'>\n" +  // @{/login}
                "      <table>\n" +
                "         <tr>\n" +
                "            <td>User:</td>\n" +
                "            <td><input type='text' name='username' id='username' value=''></td>\n" +
                "         </tr>\n" +
                "         <tr>\n" +
                "            <td>Password:</td>\n" +
                "            <td><input type='password' name='password' id='password' /></td>\n" +
                "         </tr>\n" +
                "         <tr>\n" +
                "            <td><input name=\"submit\" type=\"submit\" value=\"submit\" /></td>\n" +
                "         </tr>\n" +
                "      </table>\n" +
                "  </form>\n" +
                "</body>\n" +
                "</html>";

        return loginForm;
    }
    @GetMapping(value = "/successful")
    public String successful(HttpServletResponse response) {

        response.setHeader("Access-Control-Allow-Origin", "*");
        context = SecurityContextHolder.getContext();
        authentication = context.getAuthentication();

        grantedAuthorities = (Collection<GrantedAuthority>) authentication.getAuthorities();
        cips_authorities = "";
        if (grantedAuthorities.toString().contains("CIPS_INVOICE")) {
            cips_authorities += ", \"CIPS_INVOICE\"";
        }
        if (grantedAuthorities.toString().contains("CIPS_STATS")) {
            cips_authorities += ", \"CIPS_STATS\"";
        }
        if (cips_authorities.length() > 2) {
            cips_authorities = "[" + cips_authorities.substring(2) + "]";
        }

        String userInformation = "{" +
                "\"userName\":\"" + authentication.getName() + "\"," +
                "\"authorities\":" + cips_authorities + "," +
                "\"authenticated\":\"" + authentication.isAuthenticated() + "\"" +
                "}";

        return userInformation;
    }

    @GetMapping(value = "/logout")
    public String logout() {
        context = SecurityContextHolder.getContext();

        String error = "{" +
                "\"userName\":\"" + authentication.getName() + "\"," +
                "\"authenticated\":\"" + authentication.isAuthenticated() + "\"" +
                "}";

        return error;
    }

    @GetMapping(value = "/active")
    public String active() {
        context = SecurityContextHolder.getContext();
        authentication = context.getAuthentication();

        String userInformation = "{" +
                "userName:" + authentication.getName() + "," +
                "authenticated:" + authentication.isAuthenticated() +
                "}";

        return userInformation;
    }

    @GetMapping(value = "/loggedOut")
    public String logedout() {
        context = SecurityContextHolder.getContext();
        authentication = context.getAuthentication();

        String userInformation = "{" +
                "\"session\":\"logged out\"" +
                "}";

        return userInformation;
    }

    @GetMapping(value = "/failed")
    public String failed() {
        context = SecurityContextHolder.getContext();
        authentication = context.getAuthentication();

        String error = "{" +
                "\"userName\":\"" + authentication.getName() + "\"," +
                "\"authenticated\":\"" + authentication.isAuthenticated() + "\"" +
                "}";

        return error;
    }

    @GetMapping(value = "/invalidSession")
    public String invalidSession() {
        String error = "{" +
                "\"session\":\"invalid\"" +
                "}";
        return error;
    }
}

BasicConfiguration.java基本配置.java

@Configuration
@EnableWebSecurity
//@EnableGlobalMethodSecurity(prePostEnabled = true)
public class BasicConfiguration extends WebSecurityConfigurerAdapter {

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf()
                .disable()
                .authorizeRequests()
                .antMatchers("/login/**")
                .permitAll()
                .antMatchers("/error/**")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .defaultSuccessUrl("/login/successful", true)
                .and()
                .formLogin()
                .loginPage("/login/loginPage")
                .permitAll()
                .and()
                .formLogin()
                .failureUrl("/login/failed")
                .and()
                .logout()
                .and()
                .httpBasic()
                .and()
                .x509()
                .disable();

        http.logout()
                .logoutUrl("/login/logout")
                .logoutSuccessUrl("/login/loggedOut")
                .clearAuthentication(true)
                .deleteCookies("JSESSIONID")
                .invalidateHttpSession(true)
                .permitAll()
                .invalidateHttpSession(true);


        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
        http.sessionManagement().maximumSessions(1);
        http.sessionManagement().invalidSessionUrl("/login/invalidSession");
        http.sessionManagement().sessionFixation().newSession();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource () {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedOrigins(Arrays.asList("*"));
        corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST"));
        corsConfiguration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-   token"));

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

}

Postman Results Postman 结果

Request: 
Headers: 
  Content-Type: application/x-www-form-urlencoded
Body:
  username: <my-username>
  password: <my-password>

Response:
Headers:
  Date: Fri, 18 Oct 2019 06:44:50 GMT
  Access-Control-Allow-Origin: *
  Expires: Thu, 01 Jan 1970 00:00:00 GMT
  Content-Typ: text/plain;charset=utf-8"
  X-Content-Type-Options: nosniff
  X-XSS-Protection: 1; mode=block"
  X-Frame-Options: DENY
  Content-Lenght: 91
Cookies:
  Name: JSESSIONID
  Value: node01hidiu16ks9zn1owypmdrimsxw3.node0
Body:
  {"userName":"<my-username>","authorities":["CIPS_INVOICE", "CIPS_STATS"],"authenticated":"true"}

Angular Results Angular 结果

Code for the request: 
const formdata = new FormData();
    formdata.append('username', username);
    formdata.append('password', password);

 this.http.post(`${environment.apiUrl}/login/loginPage`, formdata, {responseType: 'text'})
               .subscribe(res => {
                 console.log('response', res);
               });

Response:
response {"userName":"anonymousUser","authorities":,"authenticated":"true"}

My Question: Why can't I get the information from the AD when I send a request from my Angular application although I can get them if I send a request via Postman?我的问题:为什么我从 Angular 应用程序发送请求时无法从 AD 获取信息,尽管如果我通过 Postman 发送请求可以获得它们? And how can I change that?我该如何改变呢?

You need to explicitly tell angular to get full response not just body.您需要明确告诉 angular 以获得完整响应,而不仅仅是正文。

By default angular httpClient return only body.默认情况下 angular httpClient 仅返回正文。 In order to get full response you need to add为了获得完整的响应,您需要添加
observe: 'response' in httpOptions something like this observe: 'response'是这样的

var headers = new Headers();
    headers.append('Content-Type', 'application/json');
    let httpOptions = new RequestOptions({ headers: headers, withCredentials: true, observe: 'response' });

 return this.http.post(url, data, httpOptions)
   .subscribe(res => {
             console.log('response', res);
           });
 });

for more details see angular docs - reading full response有关更多详细信息,请参阅angular 文档 - 阅读完整响应

I fixed the problem: It was a combination of server and client errors.我解决了这个问题:它是服务器和客户端错误的组合。

I changed the following:我更改了以下内容:

LoginController.java:登录控制器.java:

In the successfull method, I removed the response.setHeader("Access-Control-Allow-Origin", "*") call.successfull的方法中,我删除了response.setHeader("Access-Control-Allow-Origin", "*")调用。

BasicConfiguration.java基本配置.java

In the corsConfigurationSource Bean, I changed the AllowedOrigin policy: corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:4200"))corsConfigurationSource Bean 中,我更改了 AllowedOrigin 策略: corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:4200"))

and added the AllowCredentials policy: corsConfiguration.setAllowCredentials(true)并添加了 AllowCredentials 策略: corsConfiguration.setAllowCredentials(true)

Angular Angular

I changed the post call to include withCredentials: true我更改了 post call 以包含 withCredentials: true

this.http.post(`${environment.apiUrl}/login/loginPage`, formdata,
                          {withCredentials: true})
               .subscribe(res => {
                 console.log(res);
               });

Thanks to everyone who answered and tried to help:)感谢所有回答并试图提供帮助的人:)

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

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