Vue+Jwt+SpringBoot+Ldap完成登录认证的示例代码

本人野生程序员一名,了解了一些微服务架构、前后端分离、SPA的知识后就想试试做点什么东西。之前一直做后端,前端只是有基础知识。之前学习过angularjs,但当时就是一脸懵逼(完全看不懂是啥)就放弃了。最近又学了Vue,这次感觉总算明白了一些,但其中也跳过很多坑(应该还会更多),在这里写下来记录一下吧。

说回主题,之前传统登录认证的方法基本是由服务器端提供一个登录页面,页面中的一个form输入username和password后POST给服务器,服务器将这些信息与DB或Ldap中的用户信息对比,成功则将这个用户信息记录到session中。

这里我就跳了第一个大坑。传统方式前后端不分离,后端负责页面渲染,但是在前后分离的情况下,后端只负责通过暴露的RestApi提供数据,而页面的渲染、路由都由前端完成。因为rest是无状态的,因此也就不会有session记录到服务器端。

之前一直使用SpringSecurity+Cas+Ldap来做SSO,但是使用Vue做前端后我怎都想不出用之前的方法做SSO(这个坑真的爬了好久才爬出来)。后来终于想明白了上面说的session的问题(我是这么认为的也可能不对,CAS也有RestApi,但是按官网配置没成功,放弃了)。

第一个问题,该如何解决SSO的问题呢,要说到JWT。JWT是个规范,各种语言有各种语言的实现,可以去官网查到。我浅薄的理解是有一个认证服务(你自己写的,Db、Ldap什么都可以)这个认证服务会通过用户的提交信息判断认证是否成功,如果成功则查询出一些用户的信息(用户名、角色什么的),然后JWT把这些信息加密成为一个token,返回给客户端浏览器,浏览器把这些信息存储在localstorage中,以后每次访问资源都会在header中携带这个信息,服务器收到请求后使用和加密时相同的key解密密文,如果解密成功则视为用户已经认证过(当然你可以在加密时添加以一个过期时间)也就完成了SSO。使用解密出的角色信息你就可以判断这个用户是否有权限执行一些业务。这样做完后感觉好像SpringSecurity、Cas在SPA应用中的SSO似乎没什么作用了,目前我是这么认为的(当然可能不对)

第一个问题差不多解决了,来说第二个问题。之前因为有session的存在,在访问受保护的资源时如果服务器端没有当前用户的session,则会强制跳转到登录页。那在前后分离的情况下要如何实现这个需求。思路是这样的:利用Vue-Router的全局路由钩子,在访问任何页面时先判断localStorage中是否存在JWT加密后的token并且token是否过期,如果存在且没有过期则正常跳转到请求的页面,不存在或者过期则跳转到登录页重新认证。

思路说完了,上代码

1.首先你需要一个Ldap,我使用的是AD。这里我建立了一个叫minibox.com的域,并且添加了一个Employees的OU,其中有2个子OU,子OU中创建了2个用户。

在Groups中新建一些组,把之前创建的用户加入到组中,这样用户就拥有了角色。

2.搭建SpringBoot环境

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>

2.2应用配置文件

#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主配置类

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提供认证服务的类

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.前端Vue

4.1使用Vue-cli搭建项目,并使用vue-router和vue-resource,不了解的可以搜索下

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('/login')
 }
})

new Vue({
 el: '#app',
 template: '<App/>',
 components: { App },
 router,
 store
})

4.3 App.vue

<template>
 <div id="app">
  <router-view></router-view>
 </div>
</template>

<script>
 export default {
  name: 'app',
 }
</script>

<style scoped>
</style>

4.4 login.vue

<template>
  <div class="login-box">
    <div class="login-logo">
      <b>Admin</b>LTE
    </div>
    <div class="login-box-body">
      <div 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>
      </div>
      <div 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">
      </div>
      <div class="row">
        <div 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>
        </div>
      </div>
      <div class="row">
        <div 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>
        </div>
      </div>
    </div>
  </div>
</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效果

5.1访问http://localhost:8000时被导航到登录页

5.2提交登录信息并取得token,跳转下一页

到这里整个功能就完成了。本人也是菜鸟一枚,理解有错误的地方还请各位老师指正。打算把整个分布式系统的开发过程记录下来。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • spring boot+jwt实现api的token认证详解

    前言 本篇和大家分享jwt(json web token)的使用,她主要用来生成接口访问的token和验证,其单独结合springboot来开发api接口token验证很是方便,由于jwt的token中存储有用户的信息并且有加密,所以适用于分布式,这样直接吧信息存储在用户本地减速了服务端存储sessiion或token的压力: 如下快速使用: <!--jwt--> <dependency> <groupId>io.jsonwebtoken</groupId>

  • Spring Boot(四)之使用JWT和Spring Security保护REST API

    通常情况下,把API直接暴露出去是风险很大的,不说别的,直接被机器攻击就喝一壶的.那么一般来说,对API要划分出一定的权限级别,然后做一个用户的鉴权,依据鉴权结果给予用户开放对应的API.目前,比较主流的方案有几种: 用户名和密码鉴权,使用Session保存用户鉴权结果. 使用OAuth进行鉴权(其实OAuth也是一种基于Token的鉴权,只是没有规定Token的生成方式) 自行采用Token进行鉴权 第一种就不介绍了,由于依赖Session来维护状态,也不太适合移动时代,新的项目就不要采用了.

  • 详解Spring Boot实战之Filter实现使用JWT进行接口认证

    本文介绍了spring Boot实战之Filter实现使用JWT进行接口认证,分享给大家 jwt(json web token) 用户发送按照约定,向服务端发送 Header.Payload 和 Signature,并包含认证信息(密码),验证通过后服务端返回一个token,之后用户使用该token作为登录凭证,适合于移动端和api jwt使用流程 本文示例接上面几篇文章中的代码进行编写,请阅读本文的同时可以参考前面几篇文章 1.添加依赖库jjwt,本文中构造jwt及解析jwt都使用了jjwt库

  • Spring-boot结合Shrio实现JWT的方法

    本文介绍了Spring-boot结合Shrio实现JWT的方法,分享给大家,具体如下: 关于验证大致分为两个方面: 用户登录时的验证: 用户登录后每次访问时的权限认证 主要解决方法:使用自定义的Shiro Filter 项目搭建: 这是一个spring-boot 的web项目,不了解spring-boot的项目搭建,请google. pom.mx引入相关jar包 <!-- shiro 权限管理 --> <dependency> <groupId>org.apache.s

  • 基于Token的身份验证之JWT基础教程

    前言 初次了解JWT,很基础,高手勿喷. 基于Token的身份验证用来替代传统的cookie+session身份验证方法中的session. token应用流程为: 1.初次登录:用户初次登录,输入用户名密码. 2.密码验证:服务器从数据库取出用户名和密码进行验证. 3.生成JWT:服务器端验证通过,根据从数据库返回的信息,以及预设规则,生成JWT. 4.返还JWT:服务器的HTTP RESPONSE中将JWT返还. 5.带JWT的请求:以后客户端发起请求,HTTP REQUEST HEADER

  • Vue+Jwt+SpringBoot+Ldap完成登录认证的示例代码

    本人野生程序员一名,了解了一些微服务架构.前后端分离.SPA的知识后就想试试做点什么东西.之前一直做后端,前端只是有基础知识.之前学习过angularjs,但当时就是一脸懵逼(完全看不懂是啥)就放弃了.最近又学了Vue,这次感觉总算明白了一些,但其中也跳过很多坑(应该还会更多),在这里写下来记录一下吧. 说回主题,之前传统登录认证的方法基本是由服务器端提供一个登录页面,页面中的一个form输入username和password后POST给服务器,服务器将这些信息与DB或Ldap中的用户信息对比,

  • SpringBoot整合token实现登录认证的示例代码

    1.pom.xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</group

  • SpringBoot整合Sa-Token实现登录认证的示例代码

    目录 依赖 登录 退出登录 前后端分离 今天分享的是 Spring Boot 整合 Sa-Token 实现登录认证. 依赖 首先,我们需要添加依赖: 关键依赖: <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-spring-boot-starter</artifactId> <version>1.28.0</version> </depend

  • SpringBoot+Mybatis实现登录注册的示例代码

    学习SpringBoot+Mybatis实现的登录注册功能的Demo,实现这个Demo在网上也参考了资料和代码,本文是本人在实现Demo后的个人总结,以便理清思路. 1.环境 jdk8 : "1.8.0_281" Maven: 3.6.3 Idea:2020.2 Mysql:5.6.40 Navicat:10.1.7 2.步骤 2.1 创建一个SpringBoot项目 选择依赖如下: Web下的Spring Web. Template Engines下的Thymeleaf. SQL下的

  • 利用Springboot实现Jwt认证的示例代码

    JSON Web Token是目前最流行的跨域认证解决方案,,适合前后端分离项目通过Restful API进行数据交互时进行身份认证 关于Shiro整合JWT,可以看这里:Springboot实现Shiro+JWT认证 概述 由于概念性内容网上多的是,所以就不详细介绍了 具体可以看这里:阮一峰大佬的博客 我总结几个重点: JWT,全称Json Web Token,是一种令牌认证的方式 长相: 头部:放有签名算法和令牌类型(这个就是JWT) 载荷:你在令牌上附带的信息:比如用户的id,用户的电话号

  • Spring Boot 集成JWT实现前后端认证的示例代码

    目录 前言 JWT简介 为什么要用JWT 传统session认证存在那些弊端? JWT认证的优势 JWT的数据结构 Header Payload Signature Spring Boot集成JWT 引入Jwt包 编写jwt工具类 Token认证拦截器 配置拦击器 登录验证流程 示例代码 总结 前言 小程序.H5应用的快速发展,使得前后端分离已经成为了趋势,然而系统认证却是系统的重要一部分,本文将讲解JWT如何实现前后端认证. JWT简介 JWT(全称:Json Web Token)是一个开放标

  • Vue+SpringBoot实现支付宝沙箱支付的示例代码

    首先去下载支付宝沙箱的一系列东西,具体的配置什么的我就不说了,有很多博客都讲了,还有蚂蚁金服官方也说的很详细,我就直接说怎么样把后端的支付页面显示到Vue前端来: 在你配置好AlipayConfig这个文件后,就可以写前端的逻辑了,前端是采用支付宝的页面如下: 下面展示一些 内联代码片. /* 以下是支付确认html */ <div style="text=#000000 bgColor=#ffffff leftMargin=0 topMargin=4"> <head

  • SpringBoot创建JSP登录页面功能实例代码

    添加JSP配置 1.pom.xml添加jsp解析引擎 <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>javax.s

  • Vue.js中Line第三方登录api的实现代码

    国际化的项目就会用用到一些第三方的登录api,这次记录一下Line 的! 按步骤来: 注册Line账号就不说了,虽然麻烦,这就自己去想办法了! demo 请狠狠的戳这里 http://download.lllomh.com/cliect/#/product/J417081951162505 一:开发者平台配置 去Line 的开发者平台 新建一个App: https://developers.line.biz/en/ ​ ​ 顺便写好资料: ​ 动态演示: ​ 这要 用到的 就是2个: Chann

  • Vue+Element+Springboot图片上传的实现示例

    最近没事刚好联系下vue+springboot前段后分离的项目.用上了图片上传功能.记录一下. 前端待提交的表单部分代码 <el-form-item label="封面图片"> <el-upload v-model="dataForm.title" class="avatar-uploader" :limit="1" list-type="picture-card" :on-preview

随机推荐