Springboot整合shiro+jwt---实现单点登录,权限认证和控制+实现代码

shiro用来认证用户及权限控制,jwt用来生成一个token,暂存用户信息。jwt生成一个token存储在客户端,每次请求将其存储在header中,解决了跨域,可以通过自定义的方法进行验证,解决了分布式验证的问题。

大体包含哪些类

(1)LoginAction.java (controller层)。
(2)JwtUtil.java(工具类):实现了利用登陆信息生成token,更新token,根据token获取username,token验证等方法。
(3)JWTFilter.java(过滤器):拦截除登陆、注册以外的所有请求。
(4)LoginRealm.java : 实现了登陆认证,和权限获取的方法。
(5)ShiroConfig.java(配置类) :用于生成ShiroManage及将shiroRealm付给ShiroManage,并将jwtFilter添加进shiro的拦截器链中(也就是配置了哪些请求会被拦截,哪些不会)。
(6)ResponseBean.java : 用于统一格式,也就是响应给客户端的格式。
(7)JWTToken.java : JWTFilter要登录令牌LoginRealm时候,封装的一个参数(不能直接传String)。

大体结构如下:
在这里插入图片描述

后端大体流程:

  1. 用户使用username和password登陆,对password进行加密,通过username查询库中是否有该条记录,并比较加密后的密码是否相同(先只比较用户名,用户名成功再去比较密码),登陆成功后利用JwtUtil生成带过期时间的token,以后发送请求时都需要在header中添加Authorization字段附加该token信息;
  2. 结合程序实现一个JwtUtil,在其中实现利用登陆信息生成token,根据token获取username,token验证等方法;
  3. 实现一个JWTFilter继承BasicHttpAuthenticationFilter类,该拦截器需要拦截所有请求除(除登陆、注册等请求),用于判断请求是否带有token,并获取token的值传递给shiro的登陆认证方法作为参数,用于获取token;
  4. 定义LoginRealm继承AuthorizingRealm类,在其中实现登陆验证及权限获取的方法;
  5. 定义ShiroConfig配置类,用于生成ShiroManage及将shiroRealm付给ShiroManage,并配置请求拦截规则;

代码实现

前端+后台代码见如下链接
百度网盘链接:
链接:https://pan.baidu.com/s/1QRucTecIWVZsAmuqg6sEdA
提取码:wyu2

登陆部分代码如下:
1.登陆的js页面(使用layui前后端分离)

<!DOCTYPE html>
<html class="ui-page-login">
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
	</head>
	<body>
		<header class="mui-bar mui-bar-nav">
			<h1 class="mui-title">登录</h1>
		</header>
		<div class="mui-content">
			<form id='login-form' class="mui-input-group">
				<div class="mui-input-row">
					<label>账号</label>
					<input id='account' type="text" class="mui-input-clear mui-input" placeholder="请输入账号">
				</div>
				<div class="mui-input-row">
					<label>密码</label>
					<input id='password' type="password" class="mui-input-clear mui-input" placeholder="请输入密码">
				</div>
			</form>
			<div class="mui-content-padded">
				<button id='login' class="mui-btn mui-btn-block mui-btn-primary">登录</button>
				<div class="link-area">
					<a id='reg'>注册账号</a> <span class="spliter">|</span>
					<a id='forgetPassword'>忘记密码</a>
				</div>
			</div>
		</div>
		<script type="text/javascript" src="js/layui/layui.all.js" ></script>
		<script type="text/javascript" src="js/common.js" ></script>
		<script>
			var $ = layui.$;
			$("#login").click(function() {

				var username = $("#account").val();
				var password = $("#password").val();
				console.log(username);
				$.get(API_SERVER+"/login", {
					username: username,
					password: password
				}, function(res) {
					console.log(res.code);
					 switch(res.code)
					 {
					 	case 200:
					 	   layer.msg("登录成功",{icon:1,time:1000});
					 	   window.setTimeout(function(){0
					 	   	  top.location = 'index.html'; 	   	
					 	   },1000);
					 	   break;
					 	   case 500:
					 	   layer.msg("登录失败",{icon:2,time:1000});
					 	   break;
					 }
				});
			});
		</script>
	</body>
</html>

后端
2.LoginAction.java

第一次登录:前端传用户名和密码到后台,先判断数据库里是否存在,存在则再比较用户的密码是否正确,密码正确则登录成功,生成一个带过期时间的token令牌发送给客户端(下面代码省略了这个过程直接通过比较用户名和密码是否相同来判断是否登录成功)

package com.action;
import com.token.JwtUtil;
import com.vo.ResponseBean;
import jdk.nashorn.internal.parser.Token;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
@RestController
@CrossOrigin("*")
@RequestMapping("/api/")
public class LoginAction {
    @RequestMapping("/login")
    public ResponseBean login(String username, String password, HttpServletResponse response) {
       /* System.out.println("username=="+username);
        System.out.println("password=="+password);*/
        if (username != null && username.equals(password)) {
            //登录成功,产生令牌,发送给客户端
            String token = JwtUtil.generToken(username,"","");
            System.out.println("action.token"+token);
            //令牌域名(头子跨域)
            response.addHeader("Access-Control-Expose-Headers","token");
            response.addHeader("token", token);
            return new ResponseBean(200, 0, null);
        } else {
            return new ResponseBean(500, 0, null);
        }
    }
}

3.JWTFilter.java
再一次发起请求时,由JWTFilter.java对请求进行拦截。拦截的规则在ShrioConfig中配置
先得到客户端传过来的令牌,再对令牌进行更新,更新成功进入到isAccessAllowed方法进行登录判断和权限检查,登录成功而且用于权限则返回ture,否则返回false进入到onAccessDenied方法报401错误

package com.token;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
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;

/**
 *   preHandle-->isAccessAllowed(true/false)-->executeLogin(是否有异常)-->LoginRealm
 *      登录成功则访问资源
 *      登录失败则onAccessDenied(这个方法返回了一个401)
 * */
public class JWTFilter extends BasicHttpAuthenticationFilter {
    /**
     * 1,这个方法一般用于处理跨域
     * 2.更新令牌
     * 3.发送令牌给客户端
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.addHeader("Access-Control-Allow-Origin", "*");
        httpServletResponse.addHeader("Access-Control-Allow-Headers", "*");
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE");
        httpServletResponse.addHeader("Access-Control-Expose-Headers", "token");

        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }

        //得到客户端传过来的令牌
        String token = ((HttpServletRequest) request).getHeader("token");
        System.out.println("Filter.token=="+token);

        //更新令牌,更新失败返回"0"
        String newToken = JwtUtil.updateToken(token);
        System.out.println("new.token=="+newToken);

        if ("0".equals(newToken) == false) {
            httpServletResponse.addHeader("token", newToken);
        }
        //返回true则通过过滤器
        System.out.println("super.preHandle(request, response)=="+super.preHandle(request, response));
        return super.preHandle(request, response);
    }
    
    /**
     * 如果方法返回true,则登录成功,且有授权
     * 返回false则执行下面onAccessDenied
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //登录和检查权限都会交给Loginrealm进行。
        try {
            //登录 
            executeLogin(request, response);
            // 检查权限
            String url = ((HttpServletRequest) request).getRequestURI();
            getSubject(request, response).checkPermission(url);
        } catch (Exception e) {
            //e.printStackTrace();
            return false; //onAccessDenied
        }
        return true;
    }
    /**
     *登录
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String authorization = httpServletRequest.getHeader("token");
        JWTToken token = new JWTToken(authorization);
        // 提交给realm进行登入,如果错误他会抛出异常并被捕获
        getSubject(request, response).login(token);
        // 如果没有抛出异常则代表登入成功,返回true
        return true;
    }
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse r = (HttpServletResponse) response;
        r.sendError(401, "HttpServletResponse.SC_UNAUTHORIZED");
        return false;
    }
}

4.JwtUtil.java

package com.token;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
@Component
public class JwtUtil {
    //密码,绝对保密,应当写在配置文件中,这里为了方便直接写了
    public static String sercetKey = "mingtianhenganghao";
    public final static long keeptime = 18000000;

   /* @Value("${token.sercetKey}")
    public  static String sercetKey;
    @Value("${token.keeptime}")
    public static long keeptime;
*/
   /*
   *
   * */
    public static void main(String[] args) {
        String token = JwtUtil.generToken("admin", null,null);
        System.out.println("sercetKey=="+sercetKey);
        System.out.println("keeptime=="+keeptime);
      //  String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhZG1pbiIsImlhdCI6MTU3MzM1MTAzNiwiZXhwIjoxNTczMzUxMDQxfQ.6W8UpzhLMKA794zuwnN2a1aFA3Wpu2yVz976ewhhuUk";
        String username = JwtUtil.getUsername(token);
        System.out.println(username);
        System.out.println("JwtUtil.token"+token);
    }

    /**
     * 产生令牌
     * @param id  用户名
     * @param issuer  签发者
     * @param subject 主体(内容)
     * @return
     */
    public static String generToken(String id, String issuer, String subject) {
        long ttlMillis = keeptime;
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(sercetKey);
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

        JwtBuilder builder = Jwts.builder().setId(id)
                .setIssuedAt(now);
        if (subject != null) {
            builder.setSubject(subject);
        }
        if (issuer != null) {
            builder.setIssuer(issuer);
        }
        builder.signWith(signatureAlgorithm, signingKey);

        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp);
        }
        System.out.println("builder.compact()=="+builder.compact());
        return builder.compact();
    }

    /**
     * 更新令牌
     * @param token
     * @return
     */
    public static String updateToken(String token) {
        try {
            Claims claims = verifyToken(token);
            String id = claims.getId();
            String subject = claims.getSubject();
            String issuer = claims.getIssuer();
            Date date = claims.getExpiration();
            return generToken(id, issuer, subject);
        } catch (Exception ex) {
            //  ex.printStackTrace();
        }
        return "0";
    }

    /**
     * 获取用户名
     * @param token
     * @return
     */
    public static String getUsername(String token) {

        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(DatatypeConverter.parseBase64Binary(sercetKey))
                    .parseClaimsJws(token).getBody();
            return claims.getId();
        } catch (Exception e) {
            //e.printStackTrace();
        }
        return null;
    }

    public static Claims verifyToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(DatatypeConverter.parseBase64Binary(sercetKey))
                .parseClaimsJws(token).getBody();
        return claims;
    }
}

5.LoginRealm.java

package com.token;
import io.jsonwebtoken.Claims;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;
import java.util.logging.Logger;
/**
 *  令牌登录或权限类
 */

@Component
public class LoginRealm extends AuthorizingRealm {

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }


    /**
     * 管理权限
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //得登录的用户名
        String username = JwtUtil.getUsername(principals.toString());
        Logger.getLogger("ROOT").info("username:"+username);
        //查数据库,用户的权限,略
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //把当前登录的用户有哪些权限添加进来,添加哪些,就能访问哪些网址
        authorizationInfo.addStringPermission("/api/searchDep");
        //authorizationInfo.addStringPermission("/api/removeDepById");
        //authorizationInfo.addStringPermissions();
        return authorizationInfo;
    }

    /**
     * 管理登录  如果有异常,则令牌无效
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //得到客户端传过来的令牌
        String tokenString =(String) token.getPrincipal();
        //根据令牌得用户名,可以查用户库
        String username = JwtUtil.getUsername(tokenString);
        if (username==null)
        {
            throw new RuntimeException("登录失败");
        }
        return new SimpleAuthenticationInfo(tokenString, tokenString, getName());
    }
}

6.ShiroConfig.java(配置类)

package com.token;
import org.apache.commons.collections.map.LinkedMap;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
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.stereotype.Component;
import java.util.HashMap;
import java.util.Map;

@Component
public class ShiroConfig {

    @Autowired
    private LoginRealm loginRealm;
    /**
     * 使用哪种类型的
     * 安全管理器
     *
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        /*
         * 关闭shiro自带的session,详情见文档
         * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);

        // 设置自定义 realm.
        securityManager.setRealm(loginRealm);
        return securityManager;
    }
    /**
     * 配置一个拦截规则
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean factory(SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);

        Map filterMap = new HashMap();
        factoryBean.setFilters(filterMap);

        //设置我们自定义的JWT过滤器
        filterMap.put("jwt", new JWTFilter());
        Map m = new LinkedMap();
        m.put("/api/login", "anon"); //不经过我们的过滤器
        m.put("/**", "jwt,authc"); //其他的经过我们的过滤器,且必须要认证
        factoryBean.setFilterChainDefinitionMap(m);
        return factoryBean;
    }
}

7.ResponseBean .java

package com.vo;
public class ResponseBean {
    private long code;
    private long count;
    private Object data;
    public long getCode() {
        return code;
    }
    public void setCode(long code) {
        this.code = code;
    }
    public long getCount() {
        return count;
    }

    public void setCount(long count) {
        this.count = count;
    }
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    public ResponseBean(long code, long count, Object data) {
        this.code = code;
        this.count = count;
        this.data = data;
    }
}

8.token.java

package com.token;

import org.apache.shiro.authc.AuthenticationToken;

/**
 * JWTFilter要登录令牌LoginRealm时候,封装的一个参数
 * 不能直接传String
 */
public class JWTToken implements AuthenticationToken {
    private  String token;
    public JWTToken(String token)
    {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

  • 7
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
课程简介:历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring Boot、Spring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值