本例使用了springboot+dubbo+shiro(sessin管理),dubbo服务部分就不看了
一、代码:
1、pom:
4.0.0
org.example
shiro-session
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
1.4.1.RELEASE
com.demo
mysercurity-api
1.0.0-SNAPSHOT
com.alibaba.spring.boot
dubbo-spring-boot-starter
2.0.0
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.session
spring-session-data-redis
redis.clients
jedis
org.apache.httpcomponents
httpclient
4.5.6
com.github.pagehelper
pagehelper
5.1.8
com.alibaba
fastjson
1.2.31
commons-collections
commons-collections
3.2.1
commons-lang
commons-lang
2.5
com.github.sgroschupf
zkclient
0.1
log4j
log4j
slf4j-log4j12
org.slf4j
org.apache.shiro
shiro-core
1.2.2
org.apache.shiro
shiro-spring
1.2.2
org.apache.shiro
shiro-ehcache
1.2.2
spring-milestones
Spring Milestones
https://repo.spring.io/libs-milestone
false
2、appliation.properties:
server.port=7777
server.context-path=/shirodemo
#配置session过期时间
shiro.session.expireTime=5000
3、ehcache.xml:
4、config:shiro总配置类:
package com.shiro.session.config;
import javax.servlet.Filter;
import com.shiro.session.exception.MyExceptionHandler;
import com.shiro.session.realms.MyShiroRealm;
import com.shiro.session.system.MySessionManager;
import com.shiro.session.system.ShiroFormAuthenticationFilter;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
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.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.web.servlet.HandlerExceptionResolver;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Autowired
private CacheManager cacheManager;
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager((org.apache.shiro.mgt.SecurityManager) securityManager);
Map filterChainDefinitionMap = new LinkedHashMap();
//注意过滤器配置顺序
//配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl,
// authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/code/getCode", "anon");
filterChainDefinitionMap.put("/user/login", "anon");
//剩下接口全部需要认证
filterChainDefinitionMap.put("/**", "authc");
// 配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
shiroFilterFactoryBean.setLoginUrl("/home/unauth");
LinkedHashMap filtsMap=new LinkedHashMap();
filtsMap.put("authc",new ShiroFormAuthenticationFilter() );
shiroFilterFactoryBean.setFilters(filtsMap);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm realm = new MyShiroRealm();
realm.setCacheManager(getEhCacheManager());
return realm;
}
@Bean
public EhCacheManager getEhCacheManager(){
EhCacheManager ehcacheManager = new EhCacheManager();
ehcacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return ehcacheManager;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
securityManager.setSessionManager(sessionManager());
securityManager.setCacheManager(getEhCacheManager());
return securityManager;
}
/**
* 自定义sessionManager
*/
@Bean
public SessionManager sessionManager() {
return new MySessionManager();
}
/**
* 开启shiro aop注解支持
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
System.out.println("开启shiro注解");
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
/**
* 自动创建代理
* @return
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 注册全局异常处理
* @return
*/
@Bean(name = "exceptionHandler")
public HandlerExceptionResolver handlerExceptionResolver() {
return new MyExceptionHandler();
}
}
5、realms:shiro登录认证,登录接口subject.login(usernamePasswordToken)调用的方法:
package com.shiro.session.realms;
import com.demo.dto.AuthorityDTO;
import com.demo.dto.UserDTO;
import com.demo.service.UserService;
import com.shiro.session.util.MD5Util;
import org.apache.shiro.authc.*;
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.beans.factory.annotation.Autowired;
import java.security.NoSuchAlgorithmException;
import java.util.List;
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
UserDTO userInfo = (UserDTO) principals.getPrimaryPrincipal();
List authorities = userService.getAuthortiesByUserId(userInfo.getId());
// 添加权限
for (AuthorityDTO authority : authorities) {
if (authority != null){
authorizationInfo.addStringPermission(authority.getAuthorityName());
}
}
// 添加角色
return authorizationInfo;
}
/**
*
* 用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确
*
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
// 获取用户的输入的域账号
UsernamePasswordToken currentUser = (UsernamePasswordToken) token;
String userAccount = currentUser.getUsername();
// 明文密码
String password = String.valueOf(currentUser.getPassword());
//加密后的密码
String md5Password = null;
UserDTO user = userService.findByUserName(userAccount);
if(user == null){
throw new IncorrectCredentialsException();
}
try {
md5Password = MD5Util.MD5(password);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
if(!user.getPassword().equals(md5Password)){
throw new IncorrectCredentialsException();
}
return new SimpleAuthenticationInfo(user, password, getName());
}
}
6、其他系统配置:
(1)MySessionManager:获取sessionId:
package com.shiro.session.system;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
public class MySessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "X-Token";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public MySessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
// 如果请求头中有 AUTHORIZATION 则其值为sessionId
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}
(2)ShiroFormAuthenticationFilter:对于ShiroConfig中配置authc的接口做拦截认证,当token不匹配时候会走到这里:
package com.shiro.session.system;
import com.alibaba.fastjson.JSON;
import com.shiro.session.dto.CommonEnums;
import com.shiro.session.dto.ResponseMessage;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.springframework.web.bind.annotation.RequestMethod;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
public class ShiroFormAuthenticationFilter extends FormAuthenticationFilter{
Logger logger = LoggerFactory.getLogger(ShiroFormAuthenticationFilter.class);
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
String requestURI = this.getPathWithinApplication(request);
if (isLoginRequest(request, response) || requestURI.equals("/sse/getData")) {
if (isLoginSubmission(request, response)) {
if (logger.isTraceEnabled()) {
logger.trace("Login submission detected. Attempting to execute login.");
}
return executeLogin(request, response);
} else {
if (logger.isTraceEnabled()) {
logger.trace("Login page view.");
}
//allow them to see the login page ;)
return true;
}
} else {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse) response;
if(req.getMethod().equals(RequestMethod.OPTIONS.name())) {
resp.setStatus(HttpStatus.OK.value());
return true;
}
if (logger.isTraceEnabled()) {
logger.trace("Attempting to access a path which requires authentication. Forwarding to the " +
"Authentication url [" + getLoginUrl() + "]");
}
resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
resp.setHeader("Access-Control-Allow-Credentials", "true");
resp.setContentType("application/json; charset=utf-8");
resp.setCharacterEncoding("UTF-8");
PrintWriter out = resp.getWriter();
ResponseMessage result = new ResponseMessage(CommonEnums.CODE_500.code,"error","登录失效");
out.println(JSON.toJSONString(result));
out.println(JSON.toJSONString(result));
out.flush();
out.close();
return false;
}
}
}
7、handler:
package com.shiro.session.handler;
import com.alibaba.fastjson.support.spring.FastJsonJsonView;
import com.shiro.session.dto.CommonEnums;
import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
public class MyExceptionHandler implements HandlerExceptionResolver {
private static Logger log = LoggerFactory.getLogger(MyExceptionHandler.class);
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView mv = new ModelAndView();
FastJsonJsonView view = new FastJsonJsonView();
Map attributes = new HashMap();
if(e instanceof UnauthorizedException){
attributes.put("code", CommonEnums.CODE_500.code);
attributes.put("msg", "权限不够");
}else{
attributes.put("code", CommonEnums.CODE_500.code);
attributes.put("msg", "系统异常,请反馈管理员");
}
view.setAttributesMap(attributes);
mv.setView(view);
log.error("异常:" + e.getMessage(), e);
return mv;
}
}
8、utils:
(1)md5加密util:
package com.shiro.session.util;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
public class MD5Util {
private final static Logger logger = LoggerFactory.getLogger(MD5Util.class);
private final static String APP_KEY="hYHN#1son@16faEV2";
private final static String CHARSET="UTF-8";
/**
* MD5加密算法
*
* @param s
* @return
*/
public final static String MD5(String s) throws NoSuchAlgorithmException {
char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F' };
byte[] btInput = s.getBytes();
// 获得MD5摘要算法的 MessageDigest 对象
MessageDigest mdInst;
mdInst = MessageDigest.getInstance("MD5");
// 使用指定的字节更新摘要
mdInst.update(btInput);
// 获得密文
byte[] md = mdInst.digest();
// 把密文转换成十六进制的字符串形式
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
}
/**
* 编码转换
* @param content
* @param charset
* @return
* @throws UnsupportedEncodingException
*/
private static byte[] getContentBytes(String content, String charset) {
if (charset == null || "".equals(charset)) {
return content.getBytes();
}
try {
return content.getBytes(charset);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
}
}
public static String sign(String prestr){
String mysign = DigestUtils.md5Hex(getContentBytes(prestr + APP_KEY, CHARSET));
return mysign;
}
public static String signParams(Map params){
try {
if (params != null && params.size()>0){
List> list = new ArrayList(params.entrySet());
Collections.sort(list, new Comparator>() {
@Override
public int compare(Map.Entry o1, Map.Entry o2) {
return o1.getKey().compareTo(o2.getKey());
}
});
StringBuffer sb = new StringBuffer();
for (Map.Entry ent:list) {
sb.append(ent.getKey());
sb.append(ent.getValue());
}
logger.info("原字符串:{}",sb.toString());
return sign(sb.toString());
}
}catch (Exception e){
}
return "";
}
public static boolean isSign(HashMap params,String sign){
try{
String newSign = signParams(params);
logger.info("原签名:{}",sign);
logger.info("新签名:{}",newSign);
if (Objects.equals(sign,newSign)){
return true;
}
}catch (Exception e){
}
return false;
}
}
package com.shiro.session.util;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class AESUtils {
private static String iv = "HGty&6%4ojyUyhgy";//偏移量字符串必须是16位 当模式是CBC的时候必须设置偏移量
private static String Algorithm = "AES";
private static String AlgorithmProvider = "AES/CBC/PKCS5Padding"; //算法/模式/补码方式
public final static String key="FUjs@17654HGJKKn";
public static byte[] generatorKey() throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance(Algorithm);
keyGenerator.init(256);//默认128,获得无政策权限后可为192或256
SecretKey secretKey = keyGenerator.generateKey();
return secretKey.getEncoded();
}
public static IvParameterSpec getIv() throws UnsupportedEncodingException {
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes("utf-8"));
System.out.println("偏移量:"+byteToHexString(ivParameterSpec.getIV()));
return ivParameterSpec;
}
public static byte[] encrypt(String src, byte[] key) throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, InvalidAlgorithmParameterException {
SecretKey secretKey = new SecretKeySpec(key, Algorithm);
IvParameterSpec ivParameterSpec = getIv();
Cipher cipher = Cipher.getInstance(AlgorithmProvider);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
byte[] cipherBytes = cipher.doFinal(src.getBytes(Charset.forName("utf-8")));
return cipherBytes;
}
public static byte[] decrypt(String src, byte[] key) throws Exception {
SecretKey secretKey = new SecretKeySpec(key, Algorithm);
IvParameterSpec ivParameterSpec = getIv();
Cipher cipher = Cipher.getInstance(AlgorithmProvider);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
byte[] hexBytes = hexStringToBytes(src);
byte[] plainBytes = cipher.doFinal(hexBytes);
return plainBytes;
}
/**
* 解密
* @param src
* @param keyStr
* @return
* @throws Exception
*/
public static String decryptStr(String src, String keyStr) throws Exception {
byte key[] = keyStr.getBytes("utf-8");
SecretKey secretKey = new SecretKeySpec(key, Algorithm);
IvParameterSpec ivParameterSpec = getIv();
Cipher cipher = Cipher.getInstance(AlgorithmProvider);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
byte[] hexBytes = hexStringToBytes(src);
byte[] plainBytes = cipher.doFinal(hexBytes);
return new String(plainBytes,"UTF-8");
}
public static String encrypt(String src, String keyStr) throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, InvalidAlgorithmParameterException {
byte key[] = keyStr.getBytes("utf-8");
SecretKey secretKey = new SecretKeySpec(key, Algorithm);
IvParameterSpec ivParameterSpec = getIv();
Cipher cipher = Cipher.getInstance(AlgorithmProvider);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
byte[] cipherBytes = cipher.doFinal(src.getBytes(Charset.forName("utf-8")));
return new String(cipherBytes,"UTF-8");
}
public static void main(String args[]){
try {
String passwordMd5 = MD5Util.MD5("123456");
System.out.println("加密后密码:"+passwordMd5);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 将byte转换为16进制字符串
* @param src
* @return
*/
public static String byteToHexString(byte[] src) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xff;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
sb.append("0");
}
sb.append(hv);
}
return sb.toString();
}
/**
* 将16进制字符串装换为byte数组
* @param hexString
* @return
*/
public static byte[] hexStringToBytes(String hexString) {
hexString = hexString.toUpperCase();
int length = hexString.length() / 2;
char[] hexChars = hexString.toCharArray();
byte[] b = new byte[length];
for (int i = 0; i < length; i++) {
int pos = i * 2;
b[i] = (byte) (charToByte(hexChars[pos]) << 4 | (charToByte(hexChars[pos + 1]))& 0xff);
}
return b;
}
private static byte charToByte(char c) {
return (byte) "0123456789ABCDEF".indexOf(c);
}
/*public static void main(String[] args) {
try {
// byte key[] = generatorKey();
System.out.println("FUjs@17654HGJKKn".length());
// 密钥必须是16的倍数
byte key[] = "FUjs@17654HGJKKn".getBytes("utf-8");//hexStringToBytes("0123456789ABCDEF");
String src = "usersrs=111111?sdfjsalkj=1mlkjklasjdfkls?sss=sdfsjlk1123123123?sdd=453456465432165765432221351567897654132";
System.out.println("密钥:"+byteToHexString(key));
System.out.println("原字符串:"+src);
String enc = byteToHexString(encrypt(src, key));
System.out.println("加密:"+enc);
System.out.println("解密:"+decryptStr(enc, AESUtils.key));
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}*/
}
(2)session获取用户util:
package com.shiro.session.util;
import com.demo.dto.UserDTO;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
public class SessionContext {
public static String getCurrentUser(){
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (servletRequestAttributes == null) {
return null;
}
HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
return (String) httpServletRequest.getSession().getAttribute("currentUser");
}
}
9、启动类:
package com.shiro.session;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
@SpringBootApplication
@ImportResource("classpath:consumer.xml")
public class Application {
public static void main(String args[]){
SpringApplication.run(Application.class,args);
}
}
二、测试:
1、使用postman模拟浏览器:
(1)访问获取分页接口,可以看到未登录情况下请求被拒绝了:
(2)访问获取验证码接口,获取成功:
证明ShiroConfig中配置的过滤链生效;
(3)登录:
(4)再次获取分页用户接口,调用成功:
(5)退出登录:
(6)再次访问获取分页用户接口,失败:
身份验证成功
2、此时,再在浏览器中验证下
(1)访问获取所有用户(get请求)接口,由于该浏览器与服务器的session会话未建立登录成功,调用接口失败:
(2)登录一下:
(3)再次 访问之前的接口,访问成功: