Heim  >  Artikel  >  Web-Frontend  >  So verwenden Sie Vue+Jwt+SpringBoot+Ldap, um die Anmeldeauthentifizierung abzuschließen

So verwenden Sie Vue+Jwt+SpringBoot+Ldap, um die Anmeldeauthentifizierung abzuschließen

php中世界最好的语言
php中世界最好的语言Original
2018-05-28 14:56:263951Durchsuche

Dieses Mal zeige ich Ihnen, wie Sie Vue+Jwt+SpringBoot+Ldap verwenden, um die Anmeldeauthentifizierung abzuschließen. Welche Vorsichtsmaßnahmen für die Verwendung von Vue+Jwt+SpringBoot+Ldap gelten, sind hier aufgeführt Werfen wir einen Blick darauf.

Die bisherige herkömmliche Login-Authentifizierungsmethode sah grundsätzlich eine Login-Seite auf der Seite vor, in der Benutzername und Passwort eingegeben und dann per POST an den Server gesendet wurden Ldap. Bei Erfolg werden die Benutzerinformationen in der Sitzung aufgezeichnet.

Hier sprang ich in die erste große Grube. Herkömmlicherweise sind das vordere und hintere Ende nicht getrennt, und das hintere Ende ist für das Rendern der Seite verantwortlich. Wenn das vordere und hintere Ende jedoch getrennt sind, ist das hintere Ende nur für die Bereitstellung von Daten über die verfügbar gemachte RestApi verantwortlich Das Rendern und Weiterleiten der Seite erfolgt durch das Frontend. Da REST zustandslos ist, wird keine Sitzung auf dem Server aufgezeichnet.

Ich habe SpringSecurity+Cas+Ldap bereits für SSO verwendet, aber nachdem ich Vue als Frontend verwendet habe, konnte ich mir nicht vorstellen, die vorherige Methode für SSO zu verwenden (es hat lange gedauert). aus dieser Grube herauszuklettern). Später habe ich endlich das oben erwähnte Sitzungsproblem herausgefunden (ich denke schon, aber es könnte falsch sein. CAS hat auch RestApi, aber es konnte nicht gemäß der offiziellen Website konfiguriert werden, also habe ich aufgegeben).

Die erste Frage ist, wie das SSO-Problem gelöst werden kann. Sprechen wir über JWT. JWT ist eine Spezifikation und es gibt Implementierungen in verschiedenen Sprachen. Sie können es auf der offiziellen Website überprüfen. Nach meinem oberflächlichen Verständnis gibt es einen Authentifizierungsdienst (von Ihnen selbst geschrieben, Db oder Ldap kann alles sein). Dieser Authentifizierungsdienst beurteilt anhand der vom Benutzer übermittelten Informationen, ob die Authentifizierung erfolgreich ist, und fragt einige Benutzerinformationen ab (). Benutzername, Rolle) oder so etwas), und dann verschlüsselt JWT diese Informationen in einem Token und gibt sie an den Client-Browser zurück. Der Browser speichert diese Informationen bei jedem zukünftigen Zugriff auf die Ressource Der Server verwendet ihn nach Erhalt der Anfrage mit demselben Schlüssel wie bei der Verschlüsselung. Wenn die Entschlüsselung erfolgreich ist, gilt der Benutzer als authentifiziert (natürlich können Sie bei der Verschlüsselung eine Ablaufzeit hinzufügen). und SSO ist abgeschlossen. Anhand der entschlüsselten Rolleninformationen können Sie feststellen, ob dieser Benutzer die Berechtigung hat, bestimmte Dienste auszuführen. Nachdem ich das gemacht habe, kommt es mir so vor, als ob SpringSecurity und Cas bei SSO in SPA-Anwendungen keine Rolle mehr spielen. Das denke ich im Moment (das kann natürlich falsch sein)

Das erste Problem ist fast gelöst, lasst uns reden zur zweiten Frage. Bisher war der Server aufgrund der Existenz von Sitzungen gezwungen, zur Anmeldeseite zu springen, wenn er beim Zugriff auf geschützte Ressourcen nicht über die Sitzung des aktuellen Benutzers verfügte. So erreichen Sie diese Anforderung, wenn Vorder- und Rückseite getrennt sind. Die Idee ist folgende: Verwenden Sie den globalen Routing-Hook von Vue-Router, um beim Zugriff auf eine Seite zunächst festzustellen, ob ein JWT-verschlüsseltes Token im lokalen Speicher vorhanden ist und ob das Token abgelaufen ist. Wenn es existiert und nicht abgelaufen ist, wird zur angeforderten Seite gesprungen Wenn dies nicht der Fall ist, wird zur erneuten Authentifizierung auf die Anmeldeseite gesprungen.

Da die Idee nun abgeschlossen ist, beginnen wir mit dem Code

1. Zuerst benötigen Sie ein Ldap, ich verwende AD. Hier habe ich eine Domäne namens minibox.com erstellt und eine Mitarbeiter-Organisationseinheit hinzugefügt, die zwei Unter-Organisationseinheiten hat, und zwei Benutzer in der Unter-Organisationseinheit erstellt.

Erstellen Sie einige neue Gruppen in Gruppen und fügen Sie die zuvor erstellten Benutzer zu den Gruppen hinzu, sodass die Benutzer Rollen haben.

2. SpringBoot-Umgebung erstellen

2.1pom-Datei

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>minibox</groupId>
 <artifactId>an</artifactId>
 <version>0.0.1-SNAPSHOT</version>
  <!-- Inherit defaults from Spring Boot -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.1.RELEASE</version>
  </parent>
  <!-- Add typical dependencies for a web application -->
  <dependencies>
    <!-- MVC -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring boot test -->
    <dependency> 
      <groupId>org.springframework.boot</groupId> 
      <artifactId>spring-boot-starter-test</artifactId> 
      <scope>test</scope> 
    </dependency> 
    <!-- spring-boot-starter-hateoas -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-hateoas</artifactId>
    </dependency>
    <!-- 热启动 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <optional>true</optional>
    </dependency>
    <!-- JWT -->
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>0.7.0</version>
    </dependency>
    <!-- Spring Ldap -->
    <dependency>
      <groupId>org.springframework.ldap</groupId>
      <artifactId>spring-ldap-core</artifactId>
      <version>2.3.1.RELEASE</version>
    </dependency>
    <!-- fastjson -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.24</version>
    </dependency>
  </dependencies>
  <!-- Package as an executable jar -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <!-- Hot swapping -->
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <dependencies>
          <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>springloaded</artifactId>
            <version>1.2.0.RELEASE</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>
</project>

2.2 AnwendungKonfigurationsdatei

#Logging_config
logging.level.root=INFO
logging.level.org.springframework.web=WARN
logging.file=minibox.log
#server_config
#使用了SSL,并且在ldap配置中使用了ldaps,这里同时也需要把AD的证书导入到server.keystore中。具体的可以查看java的keytool工具
server.port=8443
server.ssl.key-store=classpath:server.keystore
server.ssl.key-store-password=minibox
server.ssl.key-password=minibox
#jwt
#jwt加解密时使用的key
jwt.key=minibox
#ldap_config
#ldap配置信息,注意这里的userDn一定要写这种形式。referral设置为follow,说不清用途,似乎只有连接AD时才需要配置
ldap.url=ldaps://192.168.227.128:636
ldap.base=ou=Employees,dc=minibox,dc=com
ldap.userDn=cn=Administrator,cn=Users,dc=minibox,dc=com
ldap.userPwd=qqq111!!!!
ldap.referral=follow
ldap.domainName=@minibox.com

3.Spring-Hauptkonfigurationsklasse

package an;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;
@SpringBootApplication//相当于@Configuration,@EnableAutoConfiguration,@ComponentScan
public class Application {
  /*
  * SpringLdap配置。通过@Value注解读取之前配置文件中的值
  */
  @Value("${ldap.url}")
  private String ldapUrl;
  @Value("${ldap.base}")
  private String ldapBase;
  @Value("${ldap.userDn}")
  private String ldapUserDn;
  @Value("${ldap.userPwd}")
  private String ldapUserPwd;
  @Value("${ldap.referral}")
  private String ldapReferral;
  /*
  *SpringLdap的javaConfig注入方式
  */
  @Bean
  public LdapTemplate ldapTemplate() {
    return new LdapTemplate(contextSourceTarget());
  }
  @Bean
  public LdapContextSource contextSourceTarget() {
    LdapContextSource ldapContextSource = new LdapContextSource();
    ldapContextSource.setUrl(ldapUrl);
    ldapContextSource.setBase(ldapBase);
    ldapContextSource.setUserDn(ldapUserDn);
    ldapContextSource.setPassword(ldapUserPwd);
    ldapContextSource.setReferral(ldapReferral);
    return ldapContextSource;
  }
  public static void main(String[] args) throws Exception {
    SpringApplication.run(Application.class, args);
  }
}

3.1 Klasse, die Authentifizierungsdienste bereitstellt

package an.auth;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import an.entity.Employee;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@RestController
@RequestMapping("/auth")
public class JwtAuth {
  //jwt加密密匙
  @Value("${jwt.key}")
  private String jwtKey;
  //域名后缀
  @Value("${ldap.domainName}")
  private String ldapDomainName;
  //ldap模板
  @Autowired
  private LdapTemplate ldapTemplate;
  /**
   * 将域用户属性通过EmployeeAttributesMapper填充到Employee类中,返回一个填充信息的Employee实例
   */
  private class EmployeeAttributesMapper implements AttributesMapper<Employee> {
    public Employee mapFromAttributes(Attributes attrs) throws NamingException, javax.naming.NamingException {
      Employee employee = new Employee();
      employee.setName((String) attrs.get("sAMAccountName").get());
      employee.setDisplayName((String) attrs.get("displayName").get());
      employee.setRole((String) attrs.get("memberOf").toString());
      return employee;
    }
  }
  /**
   * @param username 用户提交的名称
   * @param password 用户提交的密码
   * @return 成功返回加密后的token信息,失败返回错误HTTP状态码
   */
  @CrossOrigin//因为需要跨域访问,所以要加这个注解
  @RequestMapping(method = RequestMethod.POST)
  public ResponseEntity<String> authByAd(
      @RequestParam(value = "username") String username,
      @RequestParam(value = "password") String password) {
    //这里注意用户名加域名后缀 userDn格式:anwx@minibox.com
    String userDn = username + ldapDomainName;
    //token过期时间 4小时
    Date tokenExpired = new Date(new Date().getTime() + 60*60*4*1000);
    DirContext ctx = null;
    try {
      //使用用户名、密码验证域用户
      ctx = ldapTemplate.getContextSource().getContext(userDn, password);
      //如果验证成功根据sAMAccountName属性查询用户名和用户所属的组
      Employee employee = ldapTemplate                            .search(query().where("objectclass").is("person").and("sAMAccountName").is(username),
              new EmployeeAttributesMapper())
          .get(0);
      //使用Jwt加密用户名和用户所属组信息
      String compactJws = Jwts.builder()
          .setSubject(employee.getName())
          .setAudience(employee.getRole())
          .setExpiration(tokenExpired)
          .signWith(SignatureAlgorithm.HS512, jwtKey).compact();
      //登录成功,返回客户端token信息。这里只加密了用户名和用户角色,而displayName和tokenExpired没有加密
      Map<String, Object> userInfo = new HashMap<String, Object>();
      userInfo.put("token", compactJws);
      userInfo.put("displayName", employee.getDisplayName());
      userInfo.put("tokenExpired", tokenExpired.getTime());
      return new ResponseEntity<String>(JSON.toJSONString(userInfo , SerializerFeature.DisableCircularReferenceDetect) , HttpStatus.OK);
    } catch (Exception e) {
      //登录失败,返回失败HTTP状态码
      return new ResponseEntity<String>(HttpStatus.UNAUTHORIZED);
    } finally {
      //关闭ldap连接
      LdapUtils.closeContext(ctx);
    }
  }
}

4. Front-End-Vue

4.1 Verwenden Sie Vue-cli, um das Projekt zu erstellen und verwenden Sie vue -router und vue-resource. Wenn Sie es nicht verstehen, können Sie nach

4.2 main.js

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import VueRouter from 'vue-router'
import VueResource from 'vue-resource'
import store from './store/store'
import 'bootstrap/dist/css/bootstrap.css'
import App from './App'
import Login from './components/login'
import Hello from './components/hello'
Vue.use(VueRouter)
Vue.use(VueResource)
//Vue-resource默认以payload方式提交数据,这样设置之后以formData方式提交
Vue.http.options.emulateJSON = true;
const routes = [
 {
  path: '/login',
  component : Login
 },{
  path: '/hello',
  component: Hello
 }
]
const router = new VueRouter({
 routes
})
//默认导航到登录页
router.push('/login')
/*
全局路由钩子
访问资源时需要验证localStorage中是否存在token
以及token是否过期
验证成功可以继续跳转
失败返回登录页重新登录
 */
router.beforeEach((to, from, next) => {
 if(localStorage.token && new Date().getTime() < localStorage.tokenExpired){
  next()
 }
 else{
  next(&#39;/login&#39;)
 }
})
new Vue({
 el: &#39;#app&#39;,
 template: &#39;<App/>',
 components: { App },
 router,
 store
})

4.3 App.vue

<template>
 <p id="app">
  <router-view></router-view>
 </p>
</template>
<script>
 export default {
  name: 'app',
 }
</script>
<style scoped>
</style>

4.4 login suchen .vue

<template>
  <p class="login-box">
    <p class="login-logo">
      <b>Admin</b>LTE
    </p>
    <p class="login-box-body">
      <p class="input-group form-group has-feedback">
        <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
        <input v-model="username" type="text" class="form-control" placeholder="username">
        <span class="input-group-addon">@minibox.com</span>
      </p>
      <p class="input-group form-group has-feedback">
        <span class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></span>
        <input v-model="password" type="password" class="form-control" placeholder="Password">
      </p>
      <p class="row">
        <p class="col-sm-6 col-sm-offset-3 col-md-6 col-md-offset-3">
          <transition name="slide-fade">
            <p v-if="show">用户名或密码错误</p>
          </transition>
        </p>
      </p>
      <p class="row">
        <p class="col-sm-6 col-sm-offset-3 col-md-6 col-md-offset-3">
          <button v-on:click="auth" class="btn btn-primary btn-block btn-flat">Sign In</button>
        </p>
      </p>
    </p>
  </p>
</template>
<script>
  //提供认证服务的restApi
  var authUrl = 'https://192.168.227.1:8443/auth'
  export default {
    name: 'app',
    data() {
      return {
        username: '',
        password: '',
        show: false
      }
    },
    methods: {
      auth: function(){
        var credentials = {
          username:this.username,
          password:this.password
        }
        /*
        post方法提交username和password
        认证成功将返回的用户信息写入到localStorage,并跳转到下一页面
        失败提示认证错误
        */
        this.$http.post(authUrl, credentials).then(response => {
          localStorage.token = response.data.token
          localStorage.tokenExpired = response.data.tokenExpired
          localStorage.userDisplayName = response.data.displayName
          this.$router.push('hello')
        }, response => {
          this.show = true
        })
      }
    }
  }
</script>
<style scoped>
  p{
    text-align: center
  }
  .slide-fade-enter-active {
    transition: all .8s ease;
  }
  .slide-fade-leave-active {
    transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
  }
  .slide-fade-enter, .slide-fade-leave-to
  /* .slide-fade-leave-active for <2.1.8 */ {
    transform: translateX(10px);
    opacity: 0;
  }
  @import '../assets/css/AdminLTE.min.css'
</style>

5 Effekte

5.1 Beim Zugriff auf http://localhost:8000 werden Sie zur Anmeldeseite navigiert

5.2 Anmeldeinformationen übermitteln und Token erhalten, zur nächsten Seite springen

Ich glaube, dass Sie die Methode beherrschen, nachdem Sie den Fall in diesem Artikel gelesen haben. Weitere spannende Informationen finden Sie hier Weitere verwandte Artikel auf der chinesischen PHP-Website!

Empfohlene Lektüre:

So verwenden Sie Vue, um die AdminLTE-Vorlage zu integrieren

So erstellen Sie ein Tic-Tac-Toe-Spiel mit JS

Das obige ist der detaillierte Inhalt vonSo verwenden Sie Vue+Jwt+SpringBoot+Ldap, um die Anmeldeauthentifizierung abzuschließen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn