首页 > 解决方案 > Spring Security + Angular 7 | LDAP 身份验证

问题描述

我有一个大问题。我现在在这里坐了几天,想着如何让我的登录应用程序正常工作。我希望它将用户名和密码发送到检查它的spring boot。Spring boot 中的基本内容已经在工作(loginForm)。我在使用 Spring Boot 编程方面非常陌生,因为我之前做过 NodeJS。目标是在后端通过 LDAP 验证用户,但使用 Spring Security 而不是纯Java(虽然很容易)。整个事情当然是HTTP 请求(端点)

这是我到目前为止所拥有的:

// WebSecurityConfiguration

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeRequests()
            .anyRequest().fullyAuthenticated()
            .and()
            .formLogin()
            .loginPage("/test").permitAll()
            .usernameParameter("_username_")
            .passwordParameter("_password_")
            .and()
            .csrf()
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}

@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
            .ldapAuthentication()
            .userDnPatterns("uid={0},ou=people")
            .groupSearchBase("ou=groups")
            .contextSource()
            .url("ldap://localhost:8389/dc=springframework,dc=org")
            .and()
            .passwordCompare()
            .passwordEncoder(new LdapShaPasswordEncoder())
            .passwordAttribute("userPassword");

}

@Bean
public DefaultSpringSecurityContextSource contextSource() {
    return new DefaultSpringSecurityContextSource(Arrays.asList("ldap://localhost:8389/"), "dc=springframework,cd=org");
}

我使用 Gradle 是因为我们在公司默认使用它,所以这里是build.gradle

// build.gradle

    plugins {
    id 'org.springframework.boot' version '2.1.2.RELEASE'
    id 'java'
}

apply plugin: 'io.spring.dependency-management'


group = 'net.company'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-ldap'
    implementation 'org.springframework.boot:spring-boot-starter-data-rest'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-web-services'
    implementation 'org.springframework.boot:spring-boot-starter-websocket'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    runtimeOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'mysql:mysql-connector-java'
    compileOnly 'org.projectlombok:lombok'
    compile("org.springframework.ldap:spring-ldap-core")
    compile("org.springframework.security:spring-security-ldap")
    compile("org.springframework:spring-tx")
    compile("com.unboundid:unboundid-ldapsdk")
    compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.2.4'
    compile group: 'org.glassfish.jaxb', name: 'jaxb-core', version: '2.3.0.1'
    compile group: 'javax.xml', name: 'jaxb-impl', version: '2.1'
    compile group: 'javax.activation', name: 'activation', version: '1.1.1'
    testCompile("org.springframework.boot:spring-boot-starter-test")
    testCompile("org.springframework.security:spring-security-test")
}

这就是我现在的app.component.ts。我已经导入了 HttpClientModule 并将其添加到导入中:

// app.component.ts

import {Component} from '@angular/core';
import {HttpClient} from '@angular/common/http';

import * as Stomp from 'stompjs';
import * as SockJS from 'sockjs-client';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Memory';
  status: string;

  greeting = {};

  private serverUrl = 'http://localhost:8082/';
  private stompClient;

  constructor(private http: HttpClient) {
    this.initializeWebSocketConnection();
    http.get(this.serverUrl + 'login').subscribe(data => data);
  }

  initializeWebSocketConnection() {
    let ws = new SockJS(this.serverUrl);
    this.stompClient = Stomp.over(ws);
    let that = this;
    this.stompClient.connect({}, function(frame) {
      that.stompClient.subscribe('/chat', (message) => {
        console.log(message);
      });
    });
  }

  sendMessage(msg) {
    this.stompClient.send('/app/send/message', {}, msg);
  }

  ngOnInit() {
    this.setStatus('login');
    setTimeout(() => {
      this.sendMessage('test =>');
    }, 5000);
  }

  setStatus(status): void {
    this.status = status;
  }
}

登录应用外观

// login.component.html

    <div id="container"
     fxLayout="column"
     fxLayoutAlign="start center">
  <div class="spacer" fxFlex="10%"></div>
  <div id="login" fxFlex="25%"
       fxLayout="column"
       fxLayoutAlign="start center">
    <h1 id="loginTitle" fxFlex="35%">LOGIN</h1>
    <label for="user"></label><input id="user" placeholder="username" fxFlex="17.5%" onkeydown="if (event.keyCode == 13) document.getElementById('loginButton').click()"/>
    <div class="spacer" fxFlex="10%"></div>
    <label for="password"></label><input type="password" id="password" placeholder="password" fxFlex="17.5%" onkeydown="if (event.keyCode == 13) document.getElementById('loginButton').click()"/>
    <div class="spacer" fxFlex="10%"></div>
    <img id="loginButton" src="../../assets/login/img/right-arrow.png" fxFlex="10%" alt="">
  </div>
  <div id="spacer" fxFlex="74%"></div>
  <div id="attention" fxFlex="1%">Icons made by <a href="https://www.flaticon.com/authors/lyolya" title="Lyolya">Lyolya</a></div>
</div>

这里是那些需要有关LDAP信息的人的application.propertiesapplication.yml目前为):

// application.properties

spring.ldap.embedded.ldif=classpath:test-server.ldif
spring.ldap.embedded.base-dn=dc=springframework,dc=org
spring.ldap.embedded.port=8389
server.servlet.context-path=/
server.port=8082

如果您需要更多信息或文件,请 PM 我。 如果我们得到一个解决方案,我将重构这篇文章,以便所有初学者都容易理解。

标签: javaangularspringtypescriptldap

解决方案


我认为您需要添加自定义过滤器实现。我建议阅读Spring Security Documentation,查看第 9 点。

因此,理想情况下,由于您通过 HTTP&REST 执行此操作,因此您需要任何 JWT 库将令牌传输到 Angular 并在 Spring 后端验证它们。

您应该覆盖UsernamePasswordAuthenticationFilter 并添加您的attemptAuthenticationsuccessfulAuthentication方法的实现。

首先,您需要检查发布的用户名和密码是否正确,例如:

 try {
      LoginDto loginDto = new ObjectMapper().readValue(request.getInputStream(), LoginDto.class);
      if (getAuthenticationManager() == null) logger.warn("Authentication manager is null!");
      return getAuthenticationManager()
          .authenticate(
              new UsernamePasswordAuthenticationToken(
                  loginDto.getEmail(), loginDto.getPassword(), new ArrayList<>()));
    } catch (IOException e) {
      logger.warn(e.getMessage());
    }
    throw new AuthenticationServiceException("Credentials are invalid");

在第二个中,您将令牌颁发给您的 Angular 客户端,以便用户稍后可以将其提供给您的后端,例如:

 String userName = authResult.getName();
    try {
      User user = userService.findUserByUserName(userName);

      if (user.getAccountLocked()
          .getLockDate()
          .before(java.sql.Date.valueOf(LocalDate.now().minus(15, ChronoUnit.MINUTES)))) {
        throw new UserLockedException();
      }

      List<String> authorities = new ArrayList<>();
      user.getAuthorities().forEach(e -> authorities.add(e.getAuthority()));
      Map<String, Object> claims = new LinkedHashMap<>();
      claims.put("email", user.getEmail());
      claims.put("firstName", user.getFirstName());
      claims.put("lastName", user.getLastName());
      claims.put("authorities", authorities);
      String token =
          Jwts.builder()
              .setSubject(user.getId())
              .addClaims(claims)
              .setExpiration(
                  new Date(System.currentTimeMillis() + Long.parseLong(jwtConfig.getExpiration())))
              .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret())
              .compact();
      response.setHeader("Access-Control-Expose-Headers", "Authorization");
      response.setHeader("Authorization", "Bearer " + token);

      userService.successfulLogin(user);
      userService.resetLoginAttempts(user);

    } catch (NotFoundException e) {
      logger.warn(e.getMessage());
    }
  }

将此过滤器添加到您的 http 配置中:

        Collections.singletonList(daoAuthenticationProvider()));
    AuthenticationFilter filter =  new AuthenticationFilter(userService,manager, mapper, jwtConfig);
    filter.setFilterProcessesUrl("/login");

    http.authorizeRequests().antMatchers("/**").permitAll()
        .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and().addFilter(filter);

推荐阅读