Maison  >  Article  >  interface Web  >  Comment utiliser Vue+Jwt+SpringBoot+Ldap pour terminer l'authentification de connexion

Comment utiliser Vue+Jwt+SpringBoot+Ldap pour terminer l'authentification de connexion

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

Cette fois, je vais vous montrer comment utiliser Vue+Jwt+SpringBoot+Ldap pour terminer l'authentification de connexion Quelles sont les précautions et voici des cas pratiques.

La méthode d'authentification de connexion traditionnelle précédente fournissait essentiellement une page de connexion côté serveur. Un formulaire sur la page saisissait le nom d'utilisateur et le mot de passe, puis POST sur le serveur. Le serveur comparait ces informations avec les informations utilisateur dans la base de données ou. Ldap. En cas de succès, les informations utilisateur seront enregistrées dans la session.

C'est là que j'ai sauté dans le premier grand gouffre. De manière traditionnelle, les front-ends et les back-ends ne sont pas séparés et le back-end est responsable du rendu des pages. Cependant, lorsque les front-ends et les back-ends sont séparés, le back-end est uniquement responsable de la fourniture des données via le RestApi exposé. le rendu et le routage de la page sont complétés par le front end. Étant donné que REST est sans état, aucune session n'est enregistrée sur le serveur.

J'ai déjà utilisé SpringSecurity+Cas+Ldap pour faire du SSO, mais après avoir utilisé Vue comme front-end, je ne pouvais pas penser à utiliser la méthode précédente pour faire du SSO (cela m'a pris beaucoup de temps sortir de cette fosse). Plus tard, j'ai finalement compris le problème de session mentionné ci-dessus (je pense que oui, mais c'est peut-être faux. CAS a également RestApi, mais il n'a pas pu être configuré selon le site officiel, alors j'ai abandonné).

La première question est de savoir comment résoudre le problème du SSO. Parlons de JWT. JWT est une spécification et différentes langues ont des implémentations dans différentes langues. Vous pouvez la vérifier sur le site officiel. Ma compréhension superficielle est qu'il existe un service d'authentification (écrit par vous-même, Db, Ldap, n'importe quoi). Ce service d'authentification jugera si l'authentification a réussi en fonction des informations soumises par l'utilisateur. En cas de succès, il interrogera certaines informations utilisateur (utilisateur). nom, rôle ou autre), puis JWT crypte ces informations dans un jeton et les renvoie au navigateur client. Le navigateur stocke ces informations dans le stockage local chaque fois qu'il accède à la ressource à l'avenir, il conservera ces informations dans l'en-tête. . Le serveur l'utilise après réception de la requête. Déchiffrez le texte chiffré avec la même clé que lors du chiffrement, si le décryptage est réussi, l'utilisateur est réputé avoir été authentifié (vous pouvez bien sûr ajouter un délai d'expiration lors du chiffrement), et SSO. est terminé. À l'aide des informations de rôle déchiffrées, vous pouvez déterminer si cet utilisateur est autorisé à exécuter certains services. Après avoir fait cela, j'ai l'impression que SpringSecurity et Cas n'ont aucun rôle dans le SSO dans les applications SPA. C'est ce que je pense pour le moment (c'est peut-être faux bien sûr)

Le premier problème est presque résolu, parlons-en. à propos de la deuxième question. Auparavant, en raison de l'existence de sessions, si le serveur ne disposait pas de la session de l'utilisateur actuel lors de l'accès aux ressources protégées, il était obligé de passer à la page de connexion. Comment répondre à cette exigence lorsque l'avant et l'arrière sont séparés. L'idée est la suivante : utilisez le hook de routage global de Vue-Router pour déterminer d'abord s'il existe un jeton crypté JWT dans localStorage et si le jeton a expiré lors de l'accès à une page. S'il existe et n'a pas expiré, il passera à la page demandée. page normalement. Sinon, s’il existe ou expire, il passera à la page de connexion pour une ré-authentification.

Maintenant que l'idée est terminée, commençons par le code

1 Vous avez d'abord besoin d'un Ldap, j'utilise AD. Ici, j'ai créé un domaine appelé minibox.com, ajouté une unité d'organisation Employés, qui comporte 2 sous-unités d'organisation, et créé 2 utilisateurs dans la sous-unité d'organisation.

Créez de nouveaux groupes dans Groupes et ajoutez les utilisateurs précédemment créés aux groupes afin que les utilisateurs aient des rôles.

2. Créer un environnement SpringBoot

Fichier 2.1pom

<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>

Application 2.2Fichier de configuration

#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. Classe de configuration principale Spring

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 Classe qui fournit des services d'authentification

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.1. Utiliser Vue -cli construit le projet et utilise vue-router et vue-resource Si vous ne comprenez pas, vous pouvez rechercher

4.2 main.js

4.3 App.vue.
// 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.4 login.vue
<template>
 <p id="app">
  <router-view></router-view>
 </p>
</template>
<script>
 export default {
  name: 'app',
 }
</script>
<style scoped>
</style>

5 effets
<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.1 Lorsque vous accédez à http://localhost:8000, vous êtes dirigé vers la page de connexion

5.2 Soumettez les informations de connexion et obtenez le jeton, passez à la page suivante

Je pense que vous maîtrisez la méthode après avoir lu C'est le cas dans cet article. Pour des informations plus intéressantes, veuillez prêter attention à php Chinois Autres articles connexes en ligne !

Lecture recommandée :

Comment utiliser Vue pour intégrer le modèle AdminLTE

Comment créer un jeu de tic-tac-toe avec JS

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn