Spring
发布时间:2019-10-07 发布网站:大佬教程 code.js-code.com
大佬教程 收集整理的这篇文章主要介绍了Vue+Jwt+SpringBoot+Ldap完成登录认证的示例代码 ,大佬教程 大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
本人野生程序员一名,了解了一些微服务架构、前后端分离、SPA的知识后就想试试做点什么东西。之前一直做后端,前端只是有基础知识。之前学习过angularjs,但当时就是一脸懵逼(完全看不懂是啥)就放弃了。最近又学了Vue,这次感觉总算明白了一些,但其中也跳过很多坑(应该还会更多),在这里写下来 记录一下吧。
说回主题,之前传统登录认证的方法基本是由服务器端提供一个登录页面,页面中的一个form输入username和passwor d后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的域,并且添加了一个emplo yees的OU,其中有2个子OU,子OU中创建了2个用户。
2.搭建SpringBoot环境
2.1pom文件
<div class="jb51code">
<pre class="brush:xml;">
<proje ct xmlns="http://maven.apache.org/POM/4.0.0 " xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance " xsi:sche R_462_11845@aLOCATIO n="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> ;
4.0.0
@H_671_41
@m inibox
an@H_607 _44@
0.0.1-SNAPSHOT
org.springframework.b oot
spring-boot-starter-parent@H_607 _44@
1.5.1.RELEASE
org.springframework.b oot
spring-boot-starter-web@H_607 _44@
org.springframework.b oot
spring-boot-starter-test@H_607 _44@
test
org.springframework.b oot
spring-boot-starter-hateoas@H_607 _44@
org.springframework.b oot
spring-boot-devtools@H_607 _44@
true
io.jsonwebtoken
jjwt@H_607 _44@
0.7.0
org.springframework.ldap
spring-ldap-core@H_607 _44@
2.3.1.RELEASE
com. alibaba
fastjson@H_607 _44@
1.2.24
org.springframework.b oot
spring-boot-maven-plugin@H_607 _44@
org.springframework.b oot
spring-boot-maven-plugin@H_607 _44@
org.springframework
springloaded@H_607 _44@
1.2.0.RELEASE
2.2应用配置文件
l.root=INFO
logging.level
.o rg.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-passwor d=minibox
server.ssl. key-passwor d=minibox
jwt
jwt加解密时使用的key
jwt.key=minibox
ldap_config
ldap配置信息,注意这里的userDn一定要写这种形式。referral设置为follow,说不清用途,似乎只有连接AD时才需要配置
ldap.url=ldaps://192.168.227.128:636
ldap.b ase=ou=emplo yees,dc=minibox,dc=com
ldap.userDn=cn=Administrator,cn=Users,dc=com
ldap.userPwd=qqq111!!!!
ldap.referral=follow
ldap.domainName=@m inibox.com
3.Spring主配置类
.bean
s. factory.
Ann otation.Value;
import org.springframework
.b oot.SpringApplication;
import org.springframework
.b oot.autoconfigure.SpringBootApplication;
import org.springframework.co
ntex t.
Ann otation
.b ean;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapCo
ntex t
sourc e;
@SpringBootApplication//相当于@Configuration,@EnableAutoConfiguration,@ComponentScan
public class Application
{
/*
SpringLdap配置。通过@Value注解读取之前配置文件中的值
*/
@Value("${ ldap.url}")
private Stri ng ldapUrl;
@Value("${ ldap.b asE} ")
private Stri ng ldapBase;
@Value("${ ldap.userDn}")
private Stri ng ldapUserDn;
@Value("${ ldap.userPwD} ")
private Stri ng ldapUserPwd;
@Value("${ ldap.referral}")
private Stri ng ldapReferral;
/
SpringLdap的javaConfig注入方式
*/
@Bean
public LdapTemplate ldapTemplate() {
return new LdapTemplate(contex tsourc eTarget());
}
@Bean
public LdapContex tsourc e contex tsourc eTarget() {
LdapContex tsourc e ldapContex tsourc e = new LdapContex tsourc e();
ldapContex tsourc e.setUrl(ldapUrl);
ldapContex tsourc e.setBase(ldapBasE) ;
ldapContex tsourc e.setUserDn(ldapUserDn);
ldapContex tsourc e.setpasswor d(ldapUserPwd);
ldapContex tsourc e.setReferral(ldapReferral);
return ldapContex tsourc e;
}
public static void main(Stri ng[] args) throws Exception {
SpringApplication.run(Application.class,args);
}
}
3.1提供认证服务的类
ntext;
import org.springframework
.b ean
s. factory.
Ann otation.Autowired;
import org.springframework
.b ean
s. factory.
Ann otation.Value;
import org.springframework.
http .
http Status;
import org.springframework.
http .ResponseEntity;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.Attribute
sma pper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.web
.b ind.
Ann otation.CrossOrigin;
import org.springframework.web
.b ind.
Ann otation.
requ estMapping;
import org.springframework.web
.b ind.
Ann otation.
requ estMethod;
import org.springframework.web
.b ind.
Ann otation.
requ estParam;
import org.springframework.web
.b ind.
Ann otation.RestController;
import co
m. alibaba.fastjson.JSON;
import co
m. alibaba.fastjson.
seria lizer.
seria lizerFeature;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
import java.uti
l. Date;
import java.uti
l. HashMap;
import java.uti
l. Map;
import an.entity.
emplo yee;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@RestController
@
requ estMapping("/auth")
public class JwtAuth
{
//jwt加密密匙
@Value("${ jwt.key}")
private Stri ng jwtKey;
//域名后缀
@Value("${ ldap.domainNamE} ")
private Stri ng ldapDomainName;
//ldap模板
@Autowired
private LdapTemplate ldapTemplate;
/**
/**
@param username 用户提交的名称
@param passwor d 用户提交的密码
@return 成功返回加密后的token信息,失败返 回错误http 状态码
/
@CrossOrigin//因为需要跨域访问,所以要加这个注解
@requ estMapping(method = requ estMethod.POST)
public ResponseEntity authByAd(
@requ estParam(value = "username") Stri ng username,@requ estParam(value = "passwor d") Stri ng passwor d) {
//这里注意用户名加域名后缀 userDn格式:anwx@m inibox.com
Stri ng userDn = username + ldapDomainName;
//token过期时间 4小时
Date tokenExpired = new Date(new Date().getTime() + 60 604 1000);
DirContex t ctx = null;
try {
//使用用户名、密码验证域用户
ctx = ldapTemplate.getContex tsourc e().getContex t(userDn,passwor d);
//如果验证成功根据sAMAccountName属性查询用户名和用户所属的组
emplo yee emplo yee = ldapTemplate .search(query().where("obje ctclass").is("person").and("sAMAccountName").is(userName) ,new emplo yeeAttributesma pper())
.get(0);
//使用Jwt加密用户名和用户所属组信息
Stri ng compactJws = Jwts.b uilder()
.setSubje ct(emplo yee.getName())
.setAudience(emplo yee.getRole())
.setExpiration(tokenExpired)
.signWith(SignatureAlgorithm. HS512,jwtKey).compact();
//登录成功,返回客户端token信息。这里只加密了用户名和用户角色,而displayName和tokenExpired没有加密
Map<Stri ng,Obje ct> userInfo = new HashMap<Stri ng,Obje ct>();
userInfo.put("token",compactJws);
userInfo.put("displayName",emplo yee.getDisplayName());
userInfo.put("tokenExpired",tokenExpired.getTime());
return new ResponseEntity(JSON.toJSONStri ng(userInfo,seria lizerFeature.DisableCircularReferenceDetect),http Status. OK);
} catch (Exception E) {
//登录失败,返回失败http 状态码
return new ResponseEntity(http StatuS.U NAUTHORIZED);
} finally {
//关闭ldap连接
LdapUtils. closeContex t(ctX) ;
}
}
}
4.前端Vue
4.1使用Vue-cli搭建项目,并使用vue-router和vue-resourc e,不了解的可以搜索下
4.2 main.js
E) has been set in webpack
.b ase.conf with an alia
s.
import Vue from 'vue'
import VueRouter from 'vue-router'
import Vue
resourc e from 'vue-
resourc e'
import store from './store/store'
import 'bootstrap/dist/css/bootstrap.css'
import App from './App'
import Login from './components/login'
import
Hel lo from './components/
Hel lo'
Vue.use(VueRouter)
Vue.use(Vueresourc E)
//Vue-resourc e默认以payload方式提交数据,这样设置之后以formData方式提交
Vue.http .o ptions. emulateJSON = true;
const routes = [
{
path: '/login',component : Login
},{
path: '/Hel lo',component: Hel lo
}
]
const router = new VueRouter({
routes
})
//默认导航到登录页
router.push('/login')
/
全局路由钩子
访问资源时需要验证localStorage中是否存在token
以及token是否过期
验证成功可以继续 跳转
失败返 回登录页重新登录
/
router.b eforeEach((to,from,next) => {
if(localStorage.token && new Date().getTime() < localStorage.tokenExpired){
next()
}
else{
next('/login')
}
})
new Vue({
el: '#app',template: ' ',components: { App },router,store
})
4.3 App.vue
4.4 login.vue
AdminLTE
BACk">
@minibox.com
Mary btn-block btn-flat">Sign In