springboot+vue+shiro前后端分离未登录用户只能访问登录页面

通宵终于第一次实现了前后端分离拦截需求,未登录的用户只能访问登录页面,我做的很简单,就是用户登录完后台会传一个token给前端,前端token值为空则跳转到登录页面

shiro+springboot+vue初次使用

  • 1依赖关系
  • 2后端代码
    • shiro配置
    • 验证规则
    • 过滤器配置
    • token
    • 登录登出请求
  • 3vue前端
    • 配置token
    • 路径拦截
    • 登录存储token,退出清空token

1依赖关系

pom文件引入shiro依赖和token依赖


        
            org.apache.shiro
            shiro-spring
            1.4.1
        

        
        
            io.jsonwebtoken
            jjwt
            0.9.1
        

关系图

ShiroFilterFactoryBean
DefaultWebSecurityManager
AuthenticatingFilter
AuthorizingRealm
AuthenticationToken
token
SimpleCredentialsMatcher
subject
subject.login(jwtToken)

shiro中要使用的是ShiroFilterFactoryBean,而ShiroFilterFactoryBean的使用需要一个DefaultWebSecurityManager认证授权和AuthenticatingFilter过滤器认证授权具体实现通过AuthorizingRealm可以自定义匹配规则SimpleCredentialsMatcher,配置好securityManager之后拿到subject对象和生成的token验证,匹配成功则把token返给前端

2后端代码

shiro配置

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

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();
    }
   }

3vue前端

配置token

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

登录存储token,退出清空token

登录

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: '已取消退出'
          });
        });
			}
		}

你可能感兴趣的:(登录拦截,spring,boot,vue,shiro,前后端分离)