亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 網站 > WEB開發 > 正文

Vue + Jwt + SpringBoot + Ldap 完成登錄認證

2024-04-27 15:15:14
字體:
來源:轉載
供稿:網友

  本人野生程序員一名,了解了一些微服務架構、前后端分離、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個用戶。

  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_configlogging.level.root=INFOlogging.level.org.springframework.web=WARNlogging.file=minibox.log#server_config#使用了SSL,并且在ldap配置中使用了ldaps,這里同時也需要把AD的證書導入到server.keystore中。具體的可以查看java的keytool工具server.port=8443server.ssl.key-store=classpath:server.keystoreserver.ssl.key-store-password=miniboxserver.ssl.key-password=minibox#jwt#jwt加解密時使用的keyjwt.key=minibox#ldap_config#ldap配置信息,注意這里的userDn一定要寫這種形式。referral設置為follow,說不清用途,似乎只有連接AD時才需要配置ldap.url=ldaps://192.168.227.128:636ldap.base=ou=Employees,dc=minibox,dc=comldap.userDn=cn=Administrator,cn=Users,dc=minibox,dc=comldap.userPwd=QQq111!!!!ldap.referral=followldap.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,@ComponentScanpublic 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,跳轉下一頁

認證成功跳轉

到這里整個功能就完成了。本人也是菜鳥一枚,理解有錯誤的地方還請各位老師指正。打算把整個分布式系統的開發過程記錄下來。


上一篇:@media媒體查詢

下一篇:寫JQuery 插件

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品草莓在线免费观看| 欧美视频免费在线| 亚洲黄色在线观看| 亚洲日本成人网| 最近2019中文字幕一页二页| 久久精品色欧美aⅴ一区二区| 97色伦亚洲国产| 国产欧美一区二区三区在线看| 黑人狂躁日本妞一区二区三区| 日韩综合中文字幕| 性欧美亚洲xxxx乳在线观看| 亚洲美女精品成人在线视频| 欧美黑人xxxⅹ高潮交| 欧美激情一区二区三级高清视频| 国产日韩在线看片| 日韩在线观看免费高清完整版| 欧美高清理论片| 亚洲视频免费一区| 久久99精品久久久久久噜噜| 在线看日韩欧美| 日韩不卡中文字幕| 91av成人在线| 欧美日韩在线一区| 精品亚洲一区二区三区四区五区| 国产有码在线一区二区视频| 色噜噜国产精品视频一区二区| 亚洲综合精品伊人久久| 懂色av一区二区三区| 亚洲国产成人精品久久| 在线看福利67194| 亚洲高清不卡av| 国产欧美精品va在线观看| 欧美三级欧美成人高清www| **欧美日韩vr在线| 久久久久亚洲精品国产| 国产免费一区视频观看免费| 国产精品亚洲аv天堂网| 日本欧美一级片| 在线性视频日韩欧美| 久久久久久久电影一区| 日韩理论片久久| www.99久久热国产日韩欧美.com| 亚州成人av在线| 中文字幕日韩av综合精品| 欧美日韩亚洲高清| 日产精品99久久久久久| 日韩最新av在线| 亚洲精品久久久久久久久久久| 日韩h在线观看| 中文字幕精品一区久久久久| 日本欧美中文字幕| 久久免费精品日本久久中文字幕| 欧美亚洲国产另类| 久久久久久久久中文字幕| 91久久精品美女高潮| 日韩欧美在线字幕| 中文字幕日韩电影| 最新国产精品拍自在线播放| 91av免费观看91av精品在线| 97成人精品视频在线观看| 国产热re99久久6国产精品| 久久久久久com| 日韩欧美中文第一页| 国产欧美va欧美va香蕉在线| 亚洲一区www| 亚洲国产精品va在线看黑人动漫| 亚洲天天在线日亚洲洲精| 久久久久五月天| 亚洲国模精品私拍| 欧美老女人在线视频| 日韩高清欧美高清| 亚洲欧美日本精品| 精品网站999www| 欧美剧在线观看| 国产精品免费福利| 日韩中文字幕在线视频播放| 欧美插天视频在线播放| 97香蕉超级碰碰久久免费软件| 亚洲资源在线看| 欧美黑人又粗大| 色悠久久久久综合先锋影音下载| 国产精品av网站| 中文字幕不卡在线视频极品| 国产精品网站视频| 尤物九九久久国产精品的分类| 精品国产一区久久久| 夜夜嗨av色一区二区不卡| 日韩欧美一区二区三区久久| 久久影视电视剧免费网站| 91丝袜美腿美女视频网站| 亚洲国产91精品在线观看| 亚洲精品成人久久| 亚洲精品suv精品一区二区| 国产成人黄色av| 亚洲国产精品人久久电影| 性日韩欧美在线视频| 久久免费观看视频| 欧美激情va永久在线播放| 日韩电影在线观看免费| 亚洲一区二区中文字幕| 日韩美女av在线免费观看| 亚洲男子天堂网| 国产亚洲精品久久久久久777| 一区二区三区视频观看| 欧美在线性爱视频| 亚洲精品国精品久久99热| 一本色道久久88精品综合| 欧美激情视频给我| 欧美韩国理论所午夜片917电影| 中文国产亚洲喷潮| 国产日本欧美一区二区三区在线| 性色av一区二区三区免费| 日韩精品在线观看一区二区| 2018中文字幕一区二区三区| 国产+人+亚洲| 在线观看亚洲区| 欧美夫妻性生活视频| 欧美成人免费va影院高清| 亚洲精品影视在线观看| 欧美在线视频一区二区| 日本午夜人人精品| 精品毛片三在线观看| 亚洲奶大毛多的老太婆| 青青在线视频一区二区三区| 亚洲高清av在线| 国产精品国产亚洲伊人久久| 96精品视频在线| 日韩在线观看视频免费| 国语自产精品视频在线看抢先版图片| 欧美极品欧美精品欧美视频| 久久亚洲私人国产精品va| 性欧美暴力猛交69hd| 欧美激情乱人伦一区| 亚洲欧美中文字幕在线一区| 最近日韩中文字幕中文| 欧美日韩在线观看视频小说| 久久综合五月天| 亚洲综合在线中文字幕| 亚洲精品视频免费在线观看| 日韩精品在线免费| 亚洲欧美国产高清va在线播| 亚洲欧美日韩中文在线| 久久久999精品| 亚洲第一精品夜夜躁人人爽| 北条麻妃一区二区在线观看| 日本午夜精品理论片a级appf发布| 亚洲乱码国产乱码精品精| 免费91麻豆精品国产自产在线观看| 国产精品久久久久久久久久尿| 欧美国产日产韩国视频| 日韩va亚洲va欧洲va国产| 91麻豆国产精品| 狠狠色香婷婷久久亚洲精品| 欧美肥老妇视频| 97视频在线观看视频免费视频| 自拍偷拍亚洲欧美| 国产一区私人高清影院| 国产一区二区三区18| 日韩欧美黄色动漫| 日韩av在线电影网| 国产日韩精品在线播放| 92裸体在线视频网站| 亚洲视频axxx| 美女撒尿一区二区三区|