什么是Apache shrio

Apache Shiro 是一个强大灵活的开源安全框架,可以完全处理身份验证、授权、加密和会话管理。

Realm是Shiro的核心组建,也一样是两步走,认证和授权,在Realm中的表现为以下两个方法。

  • 认证:doGetAuthenticationInfo,核心作用判断登录信息是否正确
  • 授权:doGetAuthorizationInfo,核心作用是获取用户的权限字符串,用于后续的判断

Apache Shrio 过滤器

当 Shiro 被运用到 web 项目时,Shiro 会自动创建一些默认的过滤器对客户端请求进行过滤。以下是 Shiro 提供的部分过滤器:

过滤器

描述

anon

表示可以匿名使用

authc

表示需要认证(登录)才能使用

authcBasic

表示httpBasic认证

perms

当有多个参数时必须每个参数都通过才通过 perms[“user:add:”]

port

port[8081] 跳转到schemal://serverName:8081?queryString

rest

权限

roles

角色

ssl

表示安全的url请求

user

表示必须存在用户,当登入操作时不做检查

为什么选择shiro

  • 简单性,Shiro 在使用上较 Spring Security 更简单,更容易理解。
  • 灵活性,Shiro 可运行在 Web、EJB、IoC、Google App Engine 等任何应用环境,却不依赖这些环境。而 Spring Security 只能与 Spring 一起集成使用。
  • 可插拔,Shiro 干净的 API 和设计模式使它可以方便地与许多的其它框架和应用进行集成。Shiro 可以与诸如 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 这类第三方框架无缝集成。Spring Security 在这方面就显得有些捉衿见肘。

SpringBoot 整合Apache Shrio

在项目中引入shiro非常简单,我们只需要在pom.xml文件中引入 shiro-pring 就可以了

<!-- shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>

Apache Shrio 自定义认证Token

AuthenticationToken 用于收集用户提交的身份(如用户名)及凭据(如密码)。Shiro会调用CredentialsMatcher对象的doCredentialsMatch方法对AuthenticationInfo对象和AuthenticationToken进行匹配。匹配成功则表示主体(Subject)认证成功,否则表示认证失败。

Shiro 仅提供了一个可以直接使用的 UsernamePasswordToken,用于实现基于用户名/密码主体(Subject)身份认证。UsernamePasswordToken实现了 RememberMeAuthenticationToken 和 HostAuthenticationToken,可以实现“记住我”及“主机验证”的支持。

注意:我们的业务逻辑设计是每次调用接口,不使用session存储登录状态,而是使用在head里面存token的方式,所以不使用session,并不需要用户密码认证。

自定义Token:

package com.zzg.shrio;

import org.apache.shiro.authc.AuthenticationToken;

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

Apache Shrio 自定义Realm

Realm是shiro的核心组件,主要处理两大功能:

  • 认证 我们接收filter传过来的token,并认证login操作的token
  • 授权 获取到登录用户信息,并取得用户的权限存入roles,以便后期对接口进行操作权限验证

自定义Realm:

package com.zzg.shrio;

import java.util.HashSet;
import java.util.Set;

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.apache.shiro.util.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import com.alibaba.druid.util.StringUtils;
import com.zzg.dao.RoleRepository;
import com.zzg.dao.UserRepository;
import com.zzg.entity.Permission;
import com.zzg.entity.Role;
import com.zzg.entity.User;
import com.zzg.util.JWTUtil;

@Component
public class MyRealm extends AuthorizingRealm {
	
	@Autowired
	@Lazy
	private UserRepository userRepository;
	
	@Autowired
	@Lazy
	private RoleRepository roleRepository;

	 /**
     * 必须重写此方法,不然会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }
    
    /**
     * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
     */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// TODO Auto-generated method stub
		  System.out.println(">执行 doGetAuthorizationInfo 权限认证");
	        // principals 传过来的是 token
	        // System.out.println(">principals = "+principals);
	        String username = JWTUtil.getUsername(principals.toString());

	        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

	        //获得该用户角色
	        User user = userRepository.findByUsername(username);

	       

	        Set<String> roleSet = new HashSet<>();
	        Set<String> permissionSet = new HashSet<>();
	        // 需要将 role, permission 封装到 Set 作为 info.setRoles(), info.setStringPermissions() 的参数
	        user.getRoles().stream().forEach(item->{
	        	roleSet.add(item.getRoleName());
	        });
	        user.getRoles().stream().forEach(item->{
	        	Role role = roleRepository.findByRoleId(item.getRoleId());
	        	if(!CollectionUtils.isEmpty(role.getPermissions())){
	        		for(Permission permission : role.getPermissions()){
	        			 permissionSet.add(permission.getPermission());
	        		}
	        	}
	        });
	        // 设置该用户拥有的角色和权限
	        info.setRoles(roleSet);
	        info.setStringPermissions(permissionSet);
	        return info;
	}

	 /**
     * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。
     */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
		// TODO Auto-generated method stub
		  System.out.println(">执行 doGetAuthenticationInfo 身份认证");

	        String token = (String) authenticationToken.getCredentials();
	        System.out.println(">token "+token);
	        // 解密获得 username 用于和数据库进行对比
	        String username = JWTUtil.getUsername(token);

	        if (username == null || !JWTUtil.verify(token, username)) {
	            throw new AuthenticationException("token 认证失败");
	        }

	        // 检查用户是否存在 通过密码来判断
	        User user = userRepository.findByUsername(username);
	        if (StringUtils.isEmpty(user.getPassword())) {
	            throw new AuthenticationException("该用户不存在 ");
	        }

	        System.out.println(">getName "+getName());
	        // return new SimpleAuthenticationInfo(token, token, "MyRealm");
	        return new SimpleAuthenticationInfo(token, token, getName());
	}

}

Apache Shrio 自定义Filter

自定义shiro拦截器来控制指定请求的访问权限,并登录shiro以便认证

我们自定义shiro拦截器主要使用其中的两个方法:

  • isAccessAllowed() 判断是否可以登录到系统
  • onAccessDenied() 当isAccessAllowed()返回false时,登录被拒绝,进入此接口进行异常处理
package com.zzg.filter;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import com.alibaba.druid.util.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zzg.redis.RedisUtils;
import com.zzg.shrio.JWTToken;
import com.zzg.util.HttpContextUtil;

public class JWTFilter extends BasicHttpAuthenticationFilter {
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	 // 定义jackson对象
    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * 如果带有 token,则对 token 进行检查,否则直接通过
     * >2 接着执行 isAccessAllowed
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {
        // 判断请求的请求头是否存在 Token
        if (isLoginAttempt(request, response)) {
            //如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确
            // 不正常就会抛出异常
            try {
                // 执行登录
                executeLogin(request, response);
                return true;
            } catch (Exception e) {
                //token 错误
                responseError(response, e.getMessage());
            }
        } else {
        	try{
        	 HttpServletResponse httpResponse = (HttpServletResponse) response;
             httpResponse.setContentType("application/json;charset=utf-8");
             httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
             httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());
             httpResponse.setCharacterEncoding("UTF-8");
             //设置编码,否则中文字符在重定向时会变为空字符串
             Map<String, Object> result = new HashMap<>();
             result.put("status", 400);
             result.put("msg", "token 已经注销");
             String json = MAPPER.writeValueAsString(result);
             httpResponse.getWriter().print(json);
             	return false;
        	}catch(IOException e) {
                logger.error(e.getMessage());
            }
        }

        // 如果请求头不存在 Token,则可能是执行登陆操作或者是游客状态访问,
        // 无需检查 token,直接返回 true
        return true;
    }

    /**
     * 判断用户是否想要登入。
     * 检测 header 里面是否包含 Token 字段
     *
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        System.out.println("req.getHeader(Token)"+req.getHeader("Token"));
        String token = req.getHeader("Token");
        return RedisUtils.get(token) != null;

    }

    /**
     * 执行登陆操作
     *
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("Token");

        JWTToken jwtToken = new JWTToken(token);
        // 提交给realm进行登入,如果错误他会抛出异常并被捕获
        getSubject(request, response).login(jwtToken);
        // 如果没有抛出异常则代表登入成功,返回true
        return true;
    }

    /**
     *
     * 对跨域提供支持
     *
     * >1 请求最先从这开始执行
     *
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        // 设置 header key-value
        // httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE,aaa");
        // System.out.println(httpServletRequest.getHeader("Origin"));

        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // System.out.println( httpServletRequest.getHeader("Access-Control-Request-Headers"));

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

        return super.preHandle(request, response);
    }

    /**
     * 将非法请求跳转到 /unauthorized/**
     */
    private void responseError(ServletResponse response, String message) {
        try {

            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setContentType("application/json;charset=utf-8");
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());
            httpResponse.setCharacterEncoding("UTF-8");
            //设置编码,否则中文字符在重定向时会变为空字符串
            Map<String, Object> result = new HashMap<>();
            result.put("status", 500);
            result.put("msg", message);
            String json = MAPPER.writeValueAsString(result);
            httpResponse.getWriter().print(json);
        } catch (IOException e) {

            logger.error(e.getMessage());
        }
    }
}

Apache Shrio 配置

springboot中,组件通过@Bean的方式交由spring统一管理,在这里需要配置 securityManager,shiroFilter,AuthorizationAttributeSourceAdvisor等

package com.zzg.config;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.Filter;

import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.zzg.filter.JWTFilter;
import com.zzg.shrio.MyRealm;

@Configuration
public class ShiroConfig {
	/**
	 * 先走 filter ,然后 filter 如果检测到请求头存在 token,则用 token 去 login,走 Realm 去验证
	 */
	@Bean
	public ShiroFilterFactoryBean factory(org.apache.shiro.mgt.SecurityManager securityManager) {
		ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
		// 添加自己的过滤器并且取名为jwt
		Map<String, Filter> filterMap = new LinkedHashMap<>();
		// 设置自定义的JWT过滤器
		filterMap.put("jwt", new JWTFilter());
		factoryBean.setFilters(filterMap);
		factoryBean.setSecurityManager(securityManager);

		Map<String, String> filterRuleMap = new HashMap<>();
		filterRuleMap.put("/sys/login", "anon"); //登录接口排除
		filterRuleMap.put("/sys/logout", "anon"); //登出接口排除
		// 所有请求通过我们自己的JWT Filter
		filterRuleMap.put("/**", "jwt");
		factoryBean.setFilterChainDefinitionMap(filterRuleMap);
		return factoryBean;
	}

	/**
	 * 注入 securityManager
	 */
	@Bean
	public org.apache.shiro.mgt.SecurityManager securityManager(MyRealm customRealm) {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		// 设置自定义 realm.
		securityManager.setRealm(customRealm);
		/*
		 * 关闭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);
		return securityManager;
	}

	/**
	 * 解决 在@Controller注解的类的方法中加入@RequiresRole等shiro注解,会导致该方法无法映射请求,导致返回404。
	 */
	@Bean
	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
		// 强制使用cglib,防止重复代理和可能引起代理出错的问题
		// https://zhuanlan.zhihu.com/p/29161098
		defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
		return defaultAdvisorAutoProxyCreator;

	}
	
	/**
	 * 配置 AuthorizationAttributeSourceAdvisor 使doGetAuthorizationInfo()权限配置生效
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {
		AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
		advisor.setSecurityManager(securityManager);
		return advisor;
	}

	@Bean
	public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}

}

Apache Shrio 权限注解验证

package com.zzg.controller;

import java.util.HashMap;
import java.util.Map;

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DataController {
	@RequiresPermissions({"save"}) //没有的话 AuthorizationException
    @PostMapping("/data/save")
 	@ResponseBody
    public Map<String, Object> save(@RequestHeader("token")String token) {
        System.out.println("save");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", 200);
        map.put("msg", "当前用户有save的权力");
        return map;
    }//f603cd4348b8f1d41226e3f555d392bd

    @RequiresPermissions({"delete"}) //没有的话 AuthorizationException
    @DeleteMapping("/data/delete")
    @ResponseBody
    public Map<String, Object> delete(@RequestHeader("token")String token) {
        System.out.println("delete");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", 200);
        map.put("msg", "当前用户有delete的权力");
        return map;
    }

    @RequiresPermissions({"update"}) //没有的话 AuthorizationException
    @PutMapping("/data/update")
    @ResponseBody
    public Map<String, Object> update(@RequestHeader("token")String token) {
        System.out.println("update");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", 200);
        map.put("msg", "当前用户有update的权力");
        return map;
    }

    @RequiresPermissions({"select"}) //没有的话 AuthorizationException
    @GetMapping("/data/select")
    @ResponseBody
    public Map<String, Object> select(@RequestHeader("token")String token) {
        System.out.println("select");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", 200);
        map.put("msg", "当前用户有select的权力");
        return map;
    }

    @RequiresRoles({"vip"}) //没有的话 AuthorizationException
    @GetMapping("/data/vip")
    @ResponseBody
    public Map<String, Object> vip(@RequestHeader("token")String token) {
        System.out.println("vip");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", 200);
        map.put("msg", "当前用户有VIP角色");
        return map;
    }
    @RequiresRoles({"svip"}) //没有的话 AuthorizationException
    @GetMapping("/data/svip")
    @ResponseBody
    public Map<String, Object> svip(@RequestHeader("token")String token) {
        System.out.println("svip");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", 200);
        map.put("msg", "当前用户有SVIP角色");
        return map;
    }
    @RequiresRoles({"p"}) //没有的话 AuthorizationException
    @GetMapping("/data/p")
    @ResponseBody
    public Map<String, Object> p(@RequestHeader("token")String token) {
        System.out.println("p");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", 200);
        map.put("msg", "当前用户有P角色");
        return map;
    }
}

统一异常处理

配置项目的全局异常处理

package com.zzg.exception;

import java.util.HashMap;
import java.util.Map;

import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 全局异常处理器
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
	@ExceptionHandler(value = AuthorizationException.class)
	public Map<String, String> handleException(AuthorizationException e) {
		// e.printStackTrace();
		Map<String, String> result = new HashMap<String, String>();
		result.put("status", "400");
		// 获取错误中中括号的内容
		String message = e.getMessage();
		String msg = message.substring(message.indexOf("[") + 1, message.indexOf("]"));
		// 判断是角色错误还是权限错误
		if (message.contains("role")) {
			result.put("msg", "对不起,您没有" + msg + "角色");
		} else if (message.contains("permission")) {
			result.put("msg", "对不起,您没有" + msg + "权限");
		} else {
			result.put("msg", "对不起,您的权限有误");
		}
		return result;
	}
}

项目中使用到jwt 工具类:

package com.zzg.util;

import java.util.Date;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;

public class JWTUtil {
	// 过期时间 24 小时
	private static final long EXPIRE_TIME = 60 * 24 * 60 * 1000;

	// 密钥
	private static final String SECRET = "SHIRO+JWT";

	/**
	 * 登录时通过 loginController 生成 token, 5min后过期
	 *
	 * @param username
	 *            用户名
	 * @return 加密的token
	 */
	public static String createToken(String username) {

		try {
			Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
			Algorithm algorithm = Algorithm.HMAC256(SECRET);
			System.out.println(">algorithm " + algorithm);

			// 返回 token
			// 附带username信息
			return JWT.create().withClaim("username", username)
					// 到期时间
					.withExpiresAt(date)
					// 创建一个新的JWT,并使用给定的算法进行标记
					.sign(algorithm);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 校验 token 是否正确
	 *
	 * @param token
	 *            密钥
	 * @param username
	 *            用户名
	 * @return 是否正确
	 */
	public static boolean verify(String token, String username) {
		System.out.println(">执行 verify");

		try {
			Algorithm algorithm = Algorithm.HMAC256(SECRET);
			// 在token中附带了 username 信息
			JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
			// 验证 token
			verifier.verify(token);
			return true;
		} catch (Exception exception) {
			// 出错就返回 false
			return false;
		}
	}

	/**
	 * 根据token 获取 username 获得token中的信息,无需 secret 解密也能获得
	 *
	 * @return token 中包含的 username
	 */
	public static String getUsername(String token) {
		try {
			DecodedJWT jwt = JWT.decode(token);
			return jwt.getClaim("username").asString();
		} catch (JWTDecodeException e) {
			return null;
		}
	}
}

SpringBoot 整合JWT

在项目中引入jwt非常简单,我们只需要在pom.xml文件中引入 java-jwt 就可以了

<!-- jwt  -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.8.1</version>
        </dependency>

 

项目中使用到的Redis工具类

package com.zzg.redis;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.springframework.data.redis.core.RedisTemplate;

import com.zzg.spring.SpringContextUtil;

public class RedisUtils {
	private RedisUtils() {
    }
 
    @SuppressWarnings("unchecked")
    private static RedisTemplate<String, Object> redisTemplate = SpringContextUtil
        .getBean("redisTemplate", RedisTemplate.class);
 
    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public static boolean expire(final String key, final long timeout) {
 
        return expire(key, timeout, TimeUnit.SECONDS);
    }
 
    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return true=设置成功;false=设置失败
     */
    public static boolean expire(final String key, final long timeout, final TimeUnit unit) {
 
        Boolean ret = redisTemplate.expire(key, timeout, unit);
        return ret != null && ret;
    }
 
    /**
     * 删除单个key
     *
     * @param key 键
     * @return true=删除成功;false=删除失败
     */
    public static boolean del(final String key) {
 
        Boolean ret = redisTemplate.delete(key);
        return ret != null && ret;
    }
 
    /**
     * 删除多个key
     *
     * @param keys 键集合
     * @return 成功删除的个数
     */
    public static long del(final Collection<String> keys) {
 
        Long ret = redisTemplate.delete(keys);
        return ret == null ? 0 : ret;
    }
 
    /**
     * 存入普通对象
     *
     * @param key Redis键
     * @param value 值
     */
    public static void set(final String key, final Object value) {
 
        redisTemplate.opsForValue().set(key, value, 1, TimeUnit.MINUTES);
    }
 
    // 存储普通对象操作
 
    /**
     * 存入普通对象
     *
     * @param key 键
     * @param value 值
     * @param timeout 有效期,单位秒
     */
    public static void set(final String key, final Object value, final long timeout) {
 
        redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
    }
 
    /**
     * 获取普通对象
     *
     * @param key 键
     * @return 对象
     */
    public static Object get(final String key) {
 
        return redisTemplate.opsForValue().get(key);
    }
 
    // 存储Hash操作
 
    /**
     * 往Hash中存入数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @param value 值
     */
    public static void hPut(final String key, final String hKey, final Object value) {
 
        redisTemplate.opsForHash().put(key, hKey, value);
    }
 
    /**
     * 往Hash中存入多个数据
     *
     * @param key Redis键
     * @param values Hash键值对
     */
    public static void hPutAll(final String key, final Map<String, Object> values) {
 
        redisTemplate.opsForHash().putAll(key, values);
    }
 
    /**
     * 获取Hash中的数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public static Object hGet(final String key, final String hKey) {
 
        return redisTemplate.opsForHash().get(key, hKey);
    }
 
    /**
     * 获取多个Hash中的数据
     *
     * @param key Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public static List<Object> hMultiGet(final String key, final Collection<Object> hKeys) {
 
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

 
    // 存储Set相关操作
 
    /**
     * 往Set中存入数据
     *
     * @param key Redis键
     * @param values 值
     * @return 存入的个数
     */
    public static long sSet(final String key, final Object... values) {
        Long count = redisTemplate.opsForSet().add(key, values);
        return count == null ? 0 : count;
    }
 
    /**
     * 删除Set中的数据
     *
     * @param key Redis键
     * @param values 值
     * @return 移除的个数
     */
    public static long sDel(final String key, final Object... values) {
        Long count = redisTemplate.opsForSet().remove(key, values);
        return count == null ? 0 : count;
    }
 
    // 存储List相关操作
 
    /**
     * 往List中存入数据
     *
     * @param key Redis键
     * @param value 数据
     * @return 存入的个数
     */
    public static long lPush(final String key, final Object value) {
        Long count = redisTemplate.opsForList().rightPush(key, value);
        return count == null ? 0 : count;
    }
 
    /**
     * 往List中存入多个数据
     *
     * @param key Redis键
     * @param values 多个数据
     * @return 存入的个数
     */
    public static long lPushAll(final String key, final Collection<Object> values) {
        Long count = redisTemplate.opsForList().rightPushAll(key, values);
        return count == null ? 0 : count;
    }
 
    /**
     * 往List中存入多个数据
     *
     * @param key Redis键
     * @param values 多个数据
     * @return 存入的个数
     */
    public static long lPushAll(final String key, final Object... values) {
        Long count = redisTemplate.opsForList().rightPushAll(key, values);
        return count == null ? 0 : count;
    }
 
    /**
     * 从List中获取begin到end之间的元素
     *
     * @param key Redis键
     * @param start 开始位置
     * @param end 结束位置(start=0,end=-1表示获取全部元素)
     * @return List对象
     */
    public static List<Object> lGet(final String key, final int start, final int end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

}

SpringBoot 整合Redis

在项目中引入jwt非常简单,我们只需要在pom.xml文件中引入spring-boot-starter-data-redis就可以了

<!-- redis-->
		<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

项目中使用到的其他工具类

package com.zzg.util;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

public class HttpContextUtil {
	public static HttpServletRequest getHttpServletRequest() {
		return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
	}

	public static String getDomain() {
		HttpServletRequest request = getHttpServletRequest();
		StringBuffer url = request.getRequestURL();
		return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
	}

	public static String getOrigin() {
		HttpServletRequest request = getHttpServletRequest();
		return request.getHeader("Origin");
	}
}
package com.zzg.spring;

import org.springframework.context.ApplicationContext;


public class SpringContextUtil {
	private static ApplicationContext applicationContext;

	public static void setApplicationContext(ApplicationContext applicationContext){
		// TODO Auto-generated method stub
		if(SpringContextUtil.applicationContext == null) {
			SpringContextUtil.applicationContext = applicationContext;
		}
	}

	public static ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	// 通过class 获取bean
	public static <T> T getBean(Class<T> clazz) {
		return applicationContext.getBean(clazz);
	}
	
	// 通过name 获取bean
	public static Object getBean(String name) {
		return applicationContext.getBean(name);
	}
	
	  //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name,Class<T> clazz){
        return applicationContext.getBean(name, clazz);
    }
}

项目中涉及实体对象和Repository接口

package com.zzg.entity;

import lombok.Getter;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.Id;

/**
 * 
 * @author zzg
 *
 */
@Getter
@Setter
@Entity
public class Permission {

    @Id
    private Integer permissionId;
    private String permissionName;
    private String permission;
}
package com.zzg.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.Set;

/**
 * 
 * @author zzg
 *
 */

@Getter
@Setter
@Entity
public class Role {

    @Id
    private Integer roleId;
    private String roleName;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "role_permission",
            joinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "roleId")},
            inverseJoinColumns = {@JoinColumn(name = "PERMISSION_ID", referencedColumnName = "permissionId")})
    private Set<Permission> permissions;
}
package com.zzg.entity;

import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;

import lombok.Getter;
import lombok.Setter;

/**
 * 
 * @author zzg
 *
 */
@Getter
@Setter
@Entity
public class User {
    @Id
    private Integer userId;

    private String username;
    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "user_role",
            joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "userId")},
            inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "roleId")})
    private Set<Role> roles;

}
package com.zzg.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.zzg.entity.Permission;

/**
 * 
 * @author zzg
 *
 */
@Repository
public interface PermissionRepository extends JpaRepository<Permission, Integer> {
}
package com.zzg.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.zzg.entity.Role;

/**
 * 
 * @author zzg
 *
 */
@Repository
public interface RoleRepository extends JpaRepository<Role, Integer> {
	Role findByRoleId(Integer userId);
}
package com.zzg.dao;


import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.zzg.entity.User;


/**
 * 
 * @author zzg
 *
 */
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
    User findByUsername(String username);

    User findByUserId(Integer userId);
}

项目整体截图:

redis用户权限 redis权限认证_ide

SpringBoot程序入口

package com.zzg;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import com.zzg.spring.SpringContextUtil;


@SpringBootApplication
public class ShrioApplication {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		 ApplicationContext applicationContext = SpringApplication.run(ShrioApplication.class, args);
		 SpringContextUtil.setApplicationContext(applicationContext);
	}

}

建库脚本:

/*
 Navicat MySQL Data Transfer

 Source Server         : 192.168.1.73
 Source Server Type    : MySQL
 Source Server Version : 80015
 Source Host           : 192.168.1.73:3306
 Source Schema         : auth_shrio

 Target Server Type    : MySQL
 Target Server Version : 80015
 File Encoding         : 65001

 Date: 30/11/2020 18:42:35
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission`  (
  `permission_id` int(11) NOT NULL,
  `permission` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `permission_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`permission_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES (1, 'select', '查看');
INSERT INTO `permission` VALUES (2, 'update', '更新');
INSERT INTO `permission` VALUES (3, 'delete', '删除');
INSERT INTO `permission` VALUES (4, 'save', '新增');

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `role_id` int(11) NOT NULL,
  `role_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'svip');
INSERT INTO `role` VALUES (2, 'vip');
INSERT INTO `role` VALUES (3, 'p');

-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission`  (
  `role_id` int(11) NOT NULL,
  `permission_id` int(11) NOT NULL,
  PRIMARY KEY (`role_id`, `permission_id`) USING BTREE,
  INDEX `FKf8yllw1ecvwqy3ehyxawqa1qp`(`permission_id`) USING BTREE,
  CONSTRAINT `FKa6jx8n8xkesmjmv6jqug6bg68` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `FKf8yllw1ecvwqy3ehyxawqa1qp` FOREIGN KEY (`permission_id`) REFERENCES `permission` (`permission_id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES (1, 1);
INSERT INTO `role_permission` VALUES (2, 1);
INSERT INTO `role_permission` VALUES (3, 1);
INSERT INTO `role_permission` VALUES (1, 2);
INSERT INTO `role_permission` VALUES (2, 2);
INSERT INTO `role_permission` VALUES (1, 3);
INSERT INTO `role_permission` VALUES (1, 4);
INSERT INTO `role_permission` VALUES (2, 4);


-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `user_id` int(11) NOT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '123', 'Jack');
INSERT INTO `user` VALUES (2, '123', 'Rose');
INSERT INTO `user` VALUES (3, '123', 'Paul');

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `user_id` int(11) NOT NULL,
  `role_id` int(11) NOT NULL,
  PRIMARY KEY (`user_id`, `role_id`) USING BTREE,
  INDEX `FKa68196081fvovjhkek5m97n3y`(`role_id`) USING BTREE,
  CONSTRAINT `FK859n2jvi8ivhui0rl0esws6o` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `FKa68196081fvovjhkek5m97n3y` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1);
INSERT INTO `user_role` VALUES (2, 2);
INSERT INTO `user_role` VALUES (3, 3);

SET FOREIGN_KEY_CHECKS = 1;

效果展示:

redis用户权限 redis权限认证_apache_02

redis用户权限 redis权限认证_apache_03

redis用户权限 redis权限认证_java_04

redis用户权限 redis权限认证_java_05

Github 地址:https://github.com/zhouzhiwengang/lease_sys.git