如何使用 Spring Security + Angular 登录/验证

Posted

技术标签:

【中文标题】如何使用 Spring Security + Angular 登录/验证【英文标题】:How to login/authenticate with Spring Security + Angular 【发布时间】:2020-11-17 06:40:47 【问题描述】:

我正在尝试使用 Spring boot 2.3.1 + spring security + angular 7 来做一个 webapp。 现在,我的主要目标是如果用户想要登录(使用自定义角度登录页面),前端将数据(用户名和密码)发送到后端,我想在那里进行身份验证并将消息发送回前端(比如: OK 消息什么的)

我的项目是这样工作的: 使用 maven,我构建了 angular 前端,并从“dist”文件夹复制文件/文件夹并放入后端资源文件夹。有了这个解决方案,一切正常,现在我想添加 spring 安全部分。

SecurityConfig.java

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter 

    @Override
    protected void configure(final AuthenticationManagerBuilder auth) throws Exception 
        auth.inMemoryAuthentication()
        .withUser("user1").password("nooppassword123").roles("USER");
    

    @Override
    protected void configure(final HttpSecurity http) throws Exception 
        http
        .csrf().disable()
        .authorizeRequests()
            //.antMatchers(HttpMethod.POST, "/auth").permitAll() // I tried this but nothing
            .antMatchers(HttpMethod.GET, "/login", "/index*", "/static/**", "/*.js", "/*.json", "/*.ico", "/*.sccs", "/*.woff2", "/*.css").permitAll()
            .anyRequest().authenticated()
            .and()
          .formLogin()
            .loginPage("/")
            .loginProcessingUrl("/auth")
            .usernameParameter("username")
            .passwordParameter("password")
            .failureUrl("/index.html?error=true")
            .permitAll();
    

ApiController.java

@RestController
public class ApiController 

    @PostMapping("/auth")
    public boolean login(@RequestBody User user) 
        return user.getUserName().equals("user") && user.getPassword().equals("password"); // I would like to authenticate with auth.inMemoryAuthentication()
    

我有一个带有 2 个变量(用户名和密码)的 User.java

login.components.ts

import  Component, OnInit  from '@angular/core';
import  Router  from '@angular/router';
import  FormGroup  from '@angular/forms';
import  LoggerService  from '@app/core/services/logger.service';
import  Http  from "@angular/http";

import  FormControlHelper, Globals  from '@app/core/helpers/index';
import  loginValidation  from '@app/models/form-validations/index';
import  HttpParams, HttpHeaders from "@angular/common/http";
import  URLSearchParams  from "@angular/http"
import  MatSnackBar  from '@angular/material/snack-bar';

@Component(
    selector: 'app-login',
    templateUrl: './login.component.html',
)
export class LoginComponent implements OnInit 
    // Properties
    globals: Globals;
    public loginForm: FormGroup;
    public loginValidationModel: any;
    public waiting: boolean;
    public hidePassword: boolean;
    public params = new HttpParams();

    constructor(
        globals: Globals,
        private router: Router,
        private logger: LoggerService,
        public http : Http,
        private snackBar: MatSnackBar
    ) 
        this.loginValidationModel = loginValidation;
        this.hidePassword = true;
        this.globals = globals;

    

    openSnackBar(message: string, action: string) 
        this.snackBar.open(message, action, 
          duration: 5000,
          verticalPosition: 'top', // 'top' | 'bottom'
                horizontalPosition: 'center', //'start' | 'center' | 'end' | 'left' | 'right'
                panelClass: ['red-snackbar'],
        );
      

    ngOnInit() 
        const formGroupObj = FormControlHelper.generateFormControls(this.loginValidationModel);
        if (formGroupObj) 
            this.loginForm = new FormGroup(formGroupObj);
         else 
            this.logger.error(new Error('Error generating the form modal & validations'));
        

    

    public onSubmit() 

       let urlSearchParams = new URLSearchParams();
       urlSearchParams.append('username', this.loginForm.value.username );
       urlSearchParams.append('password', this.loginForm.value.password );
       console.log("urlSearchParams: " + urlSearchParams);

       this.http.post("auth", urlSearchParams)
        .subscribe(
                response => 
                    if(response)  //Here I always get code 200 with "OK" status, even if the username/password is bad, I don't know how to fix this part
                        this.globals.loggeduser=this.loginForm.value.username;
                        this.router.navigateByUrl('/somewhere');
                     else 
                        alert("Authentication failed");
                    
                
            );

     

我试过这个https://spring.io/guides/tutorials/spring-security-and-angular-js/ 来理解它(通过一些 Baeldung 教程),但我现在有点困惑。有人可以帮助我吗?谢谢你,祝你有美好的一天。

编辑:我构建了一个 .war 文件并使用了 Tomcat。

Edit2:更多细节和一些进展。我展示了 2 个示例,使用 1 个有效和 1 个无效的用户名/密码登录。如果用户名/密码无效,我可以看到我在 url 中得到了这个“?error”部分。我的问题:尽管有错误,但在前端我可以登录并且可以访问所有内容(子页面)。我该如何解决这个问题? (如果登录有效 - >在后端/前端重定向和验证并存储它,所以我不必登录子页面如果登录无效,不要重定向只是留在登录页面上并显示一些错误消息) 我知道我必须重做 login.component 中的“public onSubmit()”方法,但我不知道怎么做。注意:两个响应都有“OK”真和状态 200,我猜这不行

Edit3:我还有 1 个问题:如果我是正确的,使用 loginProcessingUrl 我无法使用控制器处理数据(映射到 /auth)。所以在这种情况下它是没有用的,对吧?

【问题讨论】:

您能否也提及您面临的问题。这将有助于我们理解问题 @SridharPatnaik 我在我的问题中加入了“edit2”,也许现在会更好。 【参考方案1】:

我找到了一个可能的解决方案。

我稍微修改了 SecurityConfiguration.java(添加了 succesHandler/failureHandler)

SecurityConfiguration.java

@Override
protected void configure(final HttpSecurity http) throws Exception 
    http.csrf().disable().authorizeRequests()
            .antMatchers(HttpMethod.GET, "/login", "/index*", "/static/**", "/*.js", "/*.json", "/*.ico", "/*.sccs","/*.woff2", "/*.css").permitAll()
            .anyRequest()
                .authenticated()
                .and()
            .formLogin()
                .loginPage("/")
                .loginProcessingUrl("/auth")
                .usernameParameter("username")
                .passwordParameter("password")
                .successHandler(successHandler())
                .failureHandler(failureHandler())
                .permitAll()
                .and()
            .logout().permitAll();


private AuthenticationSuccessHandler successHandler() 
    return new AuthenticationSuccessHandler() 
        @Override
        public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
                HttpServletResponse httpServletResponse, Authentication authentication)
                throws IOException, ServletException 
            httpServletResponse.getWriter().append("OK");
            httpServletResponse.setStatus(200);
        
    ;


private AuthenticationFailureHandler failureHandler() 
    return new AuthenticationFailureHandler() 
        @Override
        public void onAuthenticationFailure(HttpServletRequest httpServletRequest,
                HttpServletResponse httpServletResponse, AuthenticationException e)
                throws IOException, ServletException 
            httpServletResponse.getWriter().append("Authentication failure");
            httpServletResponse.setStatus(401);
        
    ;

之后我也更改了前端登录组件。

    let urlSearchParams = new URLSearchParams();
    urlSearchParams.append('username', this.loginForm.value.username );
    urlSearchParams.append('password', this.loginForm.value.password );


    if(this.loginForm.value.username != null && this.loginForm.value.password != null) 
        this.http.post("auth", urlSearchParams).subscribe(
            response => 
                if(response.status == 200 && response.ok == true) 
                    this.globals.loggeduser=this.loginForm.value.username;
                    this.router.navigateByUrl('/somewhere');
                 else 
                    this.openSnackBar("Wrong username or password!","");
                
             
        );
    

在这些更改之后,一切正常。

【讨论】:

以上是关于如何使用 Spring Security + Angular 登录/验证的主要内容,如果未能解决你的问题,请参考以下文章

spring security 配置了httpBasic()为啥没有弹框

Spring Security Filter 测试,身份验证在 POSTMAN 中不起作用

Spring security:Spring security 如何在 SessionRegistry 中注册新会话?

如何使用 Spring Security 模拟身份验证和授权

如何禁用 spring-security 登录屏幕?

如何使用 Spring-Security 3 和 Hibernate 4 将 spring security xml 配置 hibernate 转换为 java config