1Vue + Jwt + SpringBoot + Ldap 完成登录认证

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

  Ldap结构

  在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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值