通宵终于第一次实现了前后端分离拦截需求,未登录的用户只能访问登录页面,我做的很简单,就是用户登录完后台会传一个token给前端,前端token值为空则跳转到登录页面
pom文件引入shiro依赖和token依赖
org.apache.shiro
shiro-spring
1.4.1
io.jsonwebtoken
jjwt
0.9.1
关系图
shiro中要使用的是ShiroFilterFactoryBean,而ShiroFilterFactoryBean的使用需要一个DefaultWebSecurityManager认证授权和AuthenticatingFilter过滤器认证授权具体实现通过AuthorizingRealm可以自定义匹配规则SimpleCredentialsMatcher,配置好securityManager之后拿到subject对象和生成的token验证,匹配成功则把token返给前端
ShiroConfig.java
package bssg.wechatapp.salesvisittrack.config;
import bssg.wechatapp.salesvisittrack.realm.MyRealm;
import bssg.wechatapp.salesvisittrack.shiro.JwtFilter;
import bssg.wechatapp.salesvisittrack.shiro.MyCredentialsMatcher;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author jixinwei
* @class ShiroConfig
**/
@Configuration
public class ShiroConfig {
@Autowired
private MyRealm myRealm;
@Autowired
private MyCredentialsMatcher myCredentialsMatcher;
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
myRealm.setCredentialsMatcher(myCredentialsMatcher);
securityManager.setRealm(myRealm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager manager,JwtFilter jwtFilter){
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
filterFactoryBean.setSecurityManager(manager);
Map<String, Filter> map = new HashMap<>();
map.put("jwt", jwtFilter);
filterFactoryBean.setFilters(map);
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/login", "anon");
filterMap.put("/logout", "anon");
filterMap.put("/sys/*", "authc");
filterMap.put("/**", "jwt");
filterFactoryBean.setLoginUrl("/login");
filterFactoryBean.setFilterChainDefinitionMap(filterMap);
return filterFactoryBean;
}
//自定义过滤器
@Bean
public JwtFilter getJwtFilter() {
return new JwtFilter();
}
}
MyRealm.java
package bssg.wechatapp.salesvisittrack.realm;
import bssg.wechatapp.salesvisittrack.bean.Ops;
import bssg.wechatapp.salesvisittrack.service.LoginService;
import bssg.wechatapp.salesvisittrack.token.JwtToken;
import bssg.wechatapp.salesvisittrack.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MyRealm extends AuthorizingRealm {
@Autowired
LoginService loginService;
@Override
// 获得自己定义的token
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
/*授权*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//自定义token登录验证 使用自定义token
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//解析token获取username
JwtToken jwtToken = (JwtToken) authenticationToken;
String token = (String) jwtToken.getPrincipal();
Claims claims = JwtUtil.parseJWT(token);
String username = claims.getId();
Ops loginsso = loginService.loginsso(Integer.parseInt(username));
if (loginsso == null){
return null;
}
return new SimpleAuthenticationInfo(username, loginsso.getPASSWORD(), getName());
}
}
MyCredentialsMatcher.java
package bssg.wechatapp.salesvisittrack.shiro;
import bssg.wechatapp.salesvisittrack.service.LoginService;
import bssg.wechatapp.salesvisittrack.token.JwtToken;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/*自定义密码登录密码验证*/
@Component
public class MyCredentialsMatcher extends SimpleCredentialsMatcher {
@Autowired
private LoginService userService;
//使用自定义token
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//有token说明登录过
JwtToken jwtToken=(JwtToken) token;
if (jwtToken.getPassword() == null){
return true;
}
//获得用户输入的密码
String inPassword = new String(jwtToken.getPassword());
//获得数据库中的密码
String username = String.valueOf(info.getPrincipals());
String dbPassword=(String) info.getCredentials();
//进行密码的比对
return this.equals(dbPassword, inPassword);
}
}
JwtFilter.java
package bssg.wechatapp.salesvisittrack.shiro;
import bssg.wechatapp.salesvisittrack.token.JwtToken;
import bssg.wechatapp.salesvisittrack.utils.JwtUtil;
import bssg.wechatapp.salesvisittrack.utils.Msg;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/*过滤器*/
public class JwtFilter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String jwt = request.getHeader("Authorization");
if(StringUtils.isEmpty(jwt)) {
return null;
}
return new JwtToken(jwt);
}
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String jwt = request.getHeader("Authorization");
if(StringUtils.isEmpty(jwt)) {
return true;
} else {
// 校验jwt
Claims claim = JwtUtil.parseJWT(jwt);
if(claim == null || JwtUtil.isTokenExpired(claim.getExpiration())) {
HttpServletResponse response = (HttpServletResponse)servletResponse;
response.setContentType("application/plain;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(JSON.toJSONString(Msg.fail().add("MSG","身份已过期,请重新登录")));
return false;
}
// 执行登录
return executeLogin(servletRequest, servletResponse);
}
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
Throwable throwable = e.getCause() == null ? e : e.getCause();
String json = JSON.toJSONString( Msg.fail().add("MSG",e.getMessage()));
try {
httpServletResponse.getWriter().print(json);
} catch (IOException ioException) {
}
return false;
}
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个OPTIONS请求,这里我们给OPTIONS请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
}
token.java
package bssg.wechatapp.salesvisittrack.token;
import bssg.wechatapp.salesvisittrack.utils.JwtUtil;
import lombok.Data;
import org.apache.shiro.authc.HostAuthenticationToken;
import org.apache.shiro.authc.RememberMeAuthenticationToken;
import org.springframework.stereotype.Component;
/*自定义token*/
@Data
@Component
public class JwtToken implements HostAuthenticationToken, RememberMeAuthenticationToken {
private String token;
private char[] password;
private boolean rememberMe;
private String host;
public JwtToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return this.password;
}
public JwtToken() {
this.rememberMe = false;
}
public JwtToken(String token, char[] password) {
this(token, (char[])password, false, (String)null);
}
public JwtToken(String token, String password) {
this(token, (char[])(password != null ? password.toCharArray() : null), false, (String)null);
}
public JwtToken(String token, char[] password, String host) {
this(token, password, false, host);
}
public JwtToken(String token, String password, String host) {
this(token, password != null ? password.toCharArray() : null, false, host);
}
public JwtToken(String token, char[] password, boolean rememberMe) {
this(token, (char[])password, rememberMe, (String)null);
}
public JwtToken(String token, String password, boolean rememberMe) {
this(token, (char[])(password != null ? password.toCharArray() : null), rememberMe, (String)null);
}
public JwtToken(String token, char[] password, boolean rememberMe, String host) {
this.rememberMe = false;
this.token = token;
this.password = password;
this.rememberMe = rememberMe;
this.host = host;
}
public JwtToken(String username, String password, boolean rememberMe, String host) {
this(username, password != null ? password.toCharArray() : null, rememberMe, host);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getName());
sb.append(" - ");
sb.append(JwtUtil.parseJWT(this.token).getId());
sb.append(", rememberMe=").append(this.rememberMe);
if (this.host != null) {
sb.append(" (").append(this.host).append(")");
}
return sb.toString();
}
}
JwtUtil
package bssg.wechatapp.salesvisittrack.utils;
import io.jsonwebtoken.*;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
public class JwtUtil {
/**
* 加密
*
* @param username
* @param issuer
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String username, String issuer, String subject, long ttlMillis) {
//The JWT signature algorithm we will be using to sign the token
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//We will sign our JWT with our ApiKey secret
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary("1sf12sds21ie1inecs078j");
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
//Let's set the JWT Claims
JwtBuilder builder = Jwts.builder().setId(username)
.setIssuedAt(now)
.setSubject(subject)
.setIssuer(issuer)
.signWith(signatureAlgorithm, signingKey);
//if it has been specified, let's add the expiration
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
//Builds the JWT and serializes it to a compact, URL-safe string
return builder.compact();
}
/**
* 解密
*
* @param jwt
*/
public static Claims parseJWT(String jwt) {
//This line will throw an exception if
// it is not a signed JWS (as expected)
try {
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary("1sf12sds21ie1inecs078j"))
.parseClaimsJws(jwt)
.getBody();
return claims;
}catch (ExpiredJwtException e){
return null;
}
// System.out.println("ID: " + claims.getId());
// System.out.println("Subject: " + claims.getSubject());
// System.out.println("Issuer: " + claims.getIssuer());
// System.out.println("Expiration: " + claims.getExpiration());
}
public static boolean isTokenExpired(Date expiration) {
return expiration.before(new Date());
}
public static void main(String[] args) {
//加密
String jwt = createJWT("zhangsan", "13dsdda", "afrefsa", 1000 * 60 * 60 * 24 * 7);
System.out.println(jwt);
//解密
parseJWT(jwt);
}
}
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
public class LoginController {
@Autowired
LoginService loginService;
@ResponseBody
@RequestMapping("/login")
public Msg bmpLogin(@RequestBody String Login) throws IOException {
Login login = JSON.parseObject(Login, Login.class);
if (login.getSSO() <= 0 || login.getPASSWORD() == null) {
return Msg.fail().add("MSG", "SSO和密码不能为空");
}
Subject subject = SecurityUtils.getSubject();
String token = JwtUtil.createJWT(String.valueOf(login.getSSO()), "back", "user", 1000 * 60 * 60 * 24);
JwtToken jwtToken = new JwtToken(token, login.getPASSWORD());
try {
subject.login(jwtToken);
} catch (UnknownAccountException e) {
return Msg.fail().add("MSG", "账号不存在");
} catch (IncorrectCredentialsException e) {
return Msg.fail().add("MSG", "密码错误");
}catch (NullPointerException e){
return Msg.fail().add("MSG", "账号密码错误");
}
Ops loginsso = loginService.loginsso(login.getSSO());
Map<String, Object> map = new HashMap<>();
loginsso.setPASSWORD(null);
map.put("user", loginsso);
map.put("token", token);
return Msg.success().add("data",map);
}
@GetMapping("/logout")
public Msg logout() {
SecurityUtils.getSubject().logout();
return Msg.success();
}
}
src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
token: localStorage.getItem("token"),
userInfo: JSON.parse(localStorage.getItem("userInfo")),
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
localStorage.setItem("token", token)
},
SET_USERINFO: (state, userInfo) => {
state.userInfo = userInfo
localStorage.setItem("userInfo", JSON.stringify(userInfo))
},
REMOVE_INFO: (state) => {
state.token = ''
state.userInfo = {}
localStorage.setItem("token", '')
localStorage.setItem("userInfo", JSON.stringify(''))
}
},
getters: {
// get
getUser: state => {
return state.userInfo
},
getToken: state => {
if (state.token == null) {
return ''
} else {
return state.token
}
},
},
actions: {},
modules: {}
})
/src/router/index.js
在想要拦截的路径下面加上meta: {requireAuth:true}开启认证,如果没有token值则跳转到登录login页面
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Index from '../views/Index.vue'
import User from '../views/sys/User.vue'
import Role from '../views/sys/Role.vue'
import Menu from '../views/sys/Menu.vue'
import store from "../store/index"
import ops from "../views/sys/ops";
import dir from "../views/sys/dir";
import manager from "../views/sys/manager";
import pro from "../views/sys/pro";
import sale from "../views/sys/sale";
import dirdir from "../views/sys/dirdir";
import hossale from "../views/sys/hossale";
import mama from "../views/sys/mama";
import plan from "../views/sys/plan";
import re from "../views/sys/re";
import rela from "../views/sys/rela";
import sasa from "../views/sys/sasa";
import savehos from "../views/sys/savehos";
import uphos from "../views/sys/uphos";
import $store from "../store";
Vue.use(VueRouter)
const routes = [
{
path: '/home',
name: 'Home',
component: Home,
children: [
{
path: '/index',
name: 'Index',
meta: {
title: "首页"
},
component: Index
},
{
path: '/sys/ops',
name: 'SysRole',
component: ops,
meta: {requireAuth:true}
},
{
path: '/sys/hossale',
name: 'SysRole',
component: hossale,
meta: {requireAuth:true}
},
{
path: '/sys/savehos',
name: 'SysRole',
component: savehos,
meta: {requireAuth:true}
},
]
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth) { // 判断该路由是否需要登录权限
// console.log("获取token:"+$store.getters.getToken)
if(store.getters.getToken){ //判断本地是否存在token
next();
}else {
if(to.path === '/login'){
next();
}else {
next({
path:'/login'
})
}
}
}
else {
next();
}
/*如果本地 存在 token 则 不允许直接跳转到 登录页面*/
if(to.fullPath == "/login"){
if($store.getters.getToken){
next({
path:from.fullPath
});
}else {
next();
}
}
});
export default router
登录
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
console.log(this.loginForm)
const _this = this;
this.$axios.post('/login',this.loginForm).then(res => {
var extend = res.data.extend;
var token = extend.data.token;
var user = extend.data.user;
console.log("token:"+token)
_this.$store.commit("SET_USERINFO", user)
_this.$store.commit("SET_TOKEN", token)
if(res.data.code == 100){
this.$message.success("登录成功");
_this.$router.push("/index")
}
if(res.data.code == 200){
this.$message.error(res.data.extend.MSG);
console.log(res.data.extend.MSG);
}
})
} else {
console.log(valid)
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
console.log(123)
this.loginForm=[]
this.$refs[formName].resetFields();
},
getCaptcha() {
this.$axios.get('/captcha').then(res => {
console.log("/captcha")
console.log(res)
this.loginForm.token = res.data.data.token
this.captchaImg = res.data.data.captchaImg
this.loginForm.code = ''
})
}
},
退出
methods: {
getUserInfo() {
this.$axios.get("/sys/userInfo").then(res => {
this.userInfo = res.data.data
})
},
logout() {
this.$confirm('确认退出?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$message({
type: 'success',
message: '退出成功!'
});
this.$store.commit("REMOVE_INFO");
this.$router.replace("/login");
}).catch(() => {
this.$message({
type: 'info',
message: '已取消退出'
});
});
}
}