如何在Angular中通过Spring Security进行会话认证?

huangapple go评论60阅读模式
英文:

How to authenticate via session in the Spring Security from Angular?

问题

以下是翻译好的部分:

我有一个Angular前端应用程序和一个Spring后端。目前,似乎我的应用程序上有一个JSessionId的cookie(仅在登录时获得,而不在注册时获得,原因不明)。

我假设它会将这些cookie发送回服务器。(尽管这只是一个假设)

现在,当我向受保护的服务器发出请求时,我唯一收到的是这个“请登录”弹出窗口。

当我登录时,我的UserService记录了具有以下详细信息的用户:

然后我注意到sessionId为null。可能是什么原因?

来回答一些问题:

  • 是的,我已将{withCredentials: true}粘贴到每个请求中。(特定于Angular)
  • 是的,我已阅读文档 - 我甚至尝试粘贴了所有代码,似乎没有起作用。

我的登录控制器:

现在,我正在向后端发送请求(出现弹出窗口)如下:

如您所见,我有一个方法安全,用于授权请求。

最后:

我已广泛阅读了Spring Security文档,甚至参加了一个与之相关的课程,但我仍然无法使其正常工作。

因此,我遇到的问题是:

  • 为什么Spring无法通过会话进行身份验证,尽管已配置为这样?我的错误在哪里?

编辑:我假设直接将会话发送到Angular(在REST中,而不是在cookie中)真的不安全,对吗?我目前依赖于cookie。

编辑2:天呐,我受够了,我只是打算进行OAuth2身份验证。

英文:

So I have an Angular frontend application, and a Spring backend. Currently, it seems that I have a cookie of JSessionId on my application (which I receive only on login, and not on register, for whatever reason)
(cookies)

I assume it sends those cookies back to the server. (though that's only an assumption)

Now, when I am making a request to the protected server, the only thing I get is this "Please login" popup.
Login popup

When I log in, my UserService logs a user with such details:

UsernamePasswordAuthenticationToken [Principal=User(userId=1, name=Maksym Riabov, username=MRiabov, password={bcrypt}$2a$10$W0XJRQdfxV5XXORkr2bTluIHvFetIVBzmVp51l39T5zLCQk12RV1i, company=null, enabled=true), Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ADMIN]]

And what I've noticed is that the sessionId is null there. Why could that be?

To answer some of the questions forward:

  • Yes, I've pasted {withCredentials: true} to every request. (specific to Angular)
  • Yes, I've read documentation - I've even tried pasting all the code from it and it seems that it didn't work.

My login controller:

@GetMapping("/login")
    public ResponseEntity<String> login() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        return ResponseEntity.ok("123123");
    }

    @PostMapping("/register")
    public ResponseEntity<Map<String, String>> register(@RequestBody UserRegisterDto userDto) {
        //todo check if name taken
        User user = userMapper.toEntity2(userDto);
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        user.setEnabled(true);
        //todo remove
        Authority authority = authorityRepository.save(new Authority("ADMIN"));
        user.setAuthorities(Set.of(authority));
        //todo REMOVE!!!!

        User savedUser = userRepository.save(user);
        System.out.println("registration works!");

        return ResponseEntity.ok(Map.of("result",authority.getAuthority().getAuthority()));
    }

Now, I am sending a request to the backend (which puts the popup above) like this one:

@PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/create")
    public ResponseEntity<OnboardingPathDto> createOnboardingPath() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        // erased a bit of code here
        return ResponseEntity.ok().build();

And as you see I have a method security, which throws the request for auth.

And, the cherry at the top:

@Component
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
@RequiredArgsConstructor
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
        http
                .csrf().disable().cors().disable()
                .authorizeHttpRequests() 
                .anyRequest().permitAll() //todo this is unsafe
                .and().sessionManagement(session -> session.
                        sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                        .maximumSessions(1))//to force only one session per user
     //here I tried sessionManagement to do something, but did it do something?
                .rememberMe((rememberMe) -> rememberMe.userDetailsService(userDetailsService))
                .httpBasic(); 
        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(DaoAuthenticationProvider daoAuthenticationProvider) throws Exception {
        return new ProviderManager(daoAuthenticationProvider);
    }

    @Bean
    public DaoAuthenticationProvider prov(PasswordEncoder passwordEncoder, UserDetailsService userDetailService) throws Exception {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
        daoAuthenticationProvider.setUserDetailsService(userDetailService);
        return daoAuthenticationProvider;
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {//to force only one session per user
        return new HttpSessionEventPublisher();
    }

I've read through the Spring Security documentation far and wide, and even have taken a course in it, but I still couldn't manage to get it working.

So, what I'm struggling with:

  • Why can't Spring authenticate through the session even though it is configured to do so? Where is my error?

Edit: I assume that sending the session directly into Angular (in REST, not in cookie) is really unsafe, right? I currently rely on cookies.

Edit 2: ffs, I'm sick of it, I'm just going to do oauth2 authentication.

答案1

得分: 0

Edit: 我认为将会话直接发送到Angular(在REST中,而不是在cookie中)真的是不安全的,对吗?我目前依赖于cookie。

You are right, this is a bad idea. For sessions in an application running in a browser, only use cookies with those two flag raised (value=true):

  • secure(仅在https上交换)
  • http-only(对JavaScript隐藏)。

This means that cookies should not be accessible to the Angular code but automatically set by the browser before sending requests to the backend.

You should also implement CSRF protection (which is the default in spring-security).

Edit 2: 哎呀,我受够了,我只打算进行OAuth2身份验证。

Good idea. This is much better for security, user experience (SSO), and developper experience: most OIDC providers, either on premise (like Keycloak), or in the cloud (like Auth0, Cognito, and many others), already provide with login forms (including Multi Factor Authentication), user registration, profile edition, administration screens (like clients declaration, user roles assignement, etc.). For that:

  • configure your Spring REST API as a resource-server. I have written tutorials for this there

  • configure your Angular app either as:

    • an OAuth2 client. My favorite certified lib for Angular is angular-auth-oidc-client
    • a BFF client. Backend For Frontend is a pattern where a server-side middleware serves as the only OAuth2 client to hide tokens from the browser. Angular app won't be OAuth2: it will be secured with sessions (haha! your devil is back ;-), the middleware (something like spring-cloud-gateway with tokenRelay filter) will keep this session, associate tokens to it and replace sessions with tokens before forwarding requests to resource-server. Tutorial there.
英文:

> Edit: I assume that sending the session directly into Angular (in REST, not in cookie) is really unsafe, right? I currently rely on cookies.

You are right, this is a bad idea. For sessions in an application running in a browser, only use cookies with those two flag raised (value=true):

  • secure (exchanged only over https)
  • http-only (hidden from Javascript).

This means that cookies should not be accessible to the Angular code but automatically set by the browser before sending requests to the backend.

You should also implement CSRF protection (which is the default in spring-security).

> Edit 2: ffs, I'm sick of it, I'm just going to do oauth2 authentication.

Good idea. This is much better for security, user experience (SSO), and developper experience: most OIDC providers, either on premise (like Keycloak), or in the cloud (like Auth0, Cognito, and many others), already provide with login forms (including Multi Factor Authentication), user registration, profile edition, administration screens (like clients declaration, user roles assignement, etc.). For that:

  • configure your Spring REST API as a resource-server. I have written tutorials for this there

  • configure your Angular app either as:

    • an OAuth2 client. My favorite certified lib for Angular is angular-auth-oidc-client
    • a BFF client. Backend For Frontend is a pattern where a server-side middleware serves as the only OAuth2 client to hide tokens from the browser. Angular app won't be OAuth2: it will be secured with sessions (haha! your devil is back ;-), the middleware (something like spring-cloud-gateway with tokenRelay filter) will keep this session, associate tokens to it and replace sessions with tokens before forwarding requests to resource-server. Tutorial there.

huangapple
  • 本文由 发表于 2023年2月6日 02:41:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/75354647.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定