- 配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring-mybatis.xml,classpath*:spring-redis.xml,classpath*:spring-shiro-web.xml</param-value>
</context-param>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath*:log4j.properties</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>CORS</filter-name>
<filter-class>com.wang.ssm.utils.MyFilters</filter-class>
<init-param>
<param-name>cors.allowOrigin</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.supportedMethods</param-name>
<param-value>GET, POST, HEAD, PUT, DELETE,OPTION</param-value>
</init-param>
<init-param>
<param-name>cors.supportedHeaders</param-name>
<param-value>Accept, Origin, X-Requested-With, Content-Type, Last-Modified</param-value>
</init-param>
<init-param>
<param-name>cors.exposedHeaders</param-name>
<param-value>Set-Cookie</param-value>
</init-param>
<init-param>
<param-name>cors.supportsCredentials</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>cors.maxAge</param-name>
<param-value>3600</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORS</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
- 配置shiro
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="filters">
<util:map>
<entry key="authc" value-ref="shiroLoginFilter" />
</util:map>
</property>
<property name="unauthorizedUrl" value="/admin/unKnown"/>
<property name="filterChainDefinitions">
<value>
/admin/selectMenu = authc[admin:manage]
/**=anon
</value>
</property>
</bean>
<bean id="shiroLoginFilter" class="com.wang.ssm.utils.shiro.ShiroLoginFilter"/>
<bean id="myRealm" class="com.wang.ssm.utils.shiro.MyRealm">
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
<property name="rememberMeManager" ref="rememberMeManager"/>
<property name="sessionManager" ref="sessionManager"/>
</bean>
<bean id="sessionManager" class="com.wang.ssm.utils.shiro.MySessionManager">
<property name="globalSessionTimeout" value="21600000"/>
<property name="deleteInvalidSessions" value="true"/>
<property name="sessionValidationSchedulerEnabled" value="true"/>
</bean>
<bean id="rememberMe" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe"/>
<property name="httpOnly" value="true"/>
<property name="maxAge" value="259200"/>
</bean>
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>
<property name="cookie" ref="rememberMe"/>
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
</beans>
- 自定义realm
package com.wang.ssm.utils.shiro;
import com.auth0.jwt.internal.org.apache.commons.codec.digest.DigestUtils;
import com.auth0.jwt.internal.org.apache.commons.lang3.StringUtils;
import com.auth0.jwt.internal.org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import com.auth0.jwt.internal.org.apache.commons.lang3.builder.ToStringStyle;
import com.wang.ssm.entity.Role;
import com.wang.ssm.entity.User;
import com.wang.ssm.service.AdminService;
import com.wang.ssm.service.UserService;
import com.wang.ssm.utils.CacheInspect;
import com.wang.ssm.utils.CacheUtil;
import org.apache.shiro.SecurityUtils;
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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private AdminService adminService;
@Autowired
private CacheUtil cacheUtil;
@Override
public void setName(String name) {
super.setName("customRealm");
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken authcToken = (UsernamePasswordToken)token;
System.out.println("验证当前Subject时获取到token为" + ReflectionToStringBuilder.toString(authcToken, ToStringStyle.MULTI_LINE_STYLE));
final String username = (String) token.getPrincipal();
String password = null;
final Object credentials = token.getCredentials();
if (credentials instanceof char[]) {
password = new String((char[]) credentials);
}
User user = new User();
user.setUserName(username);
user.setPassword(password);
String userToken = null;
try {
userToken = userService.login(user);
} catch (Exception e) {
e.printStackTrace();
}
final HashMap<String, Object> principal = new HashMap<>();
principal.put("token",userToken);
principal.put("user",user);
return new SimpleAuthenticationInfo(principal, user.getPassword(), this.getName());
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Map principal = (Map) principals.getPrimaryPrincipal();
User user = (User) principal.get("user");
Map<String, List<Role>> permissions = adminService.getPermissions(user);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addStringPermission("admin:ma");
return simpleAuthorizationInfo;
}
private void setAuthenticationSession(Object value){
Subject currentUser = SecurityUtils.getSubject();
if(null != currentUser){
Session session = currentUser.getSession();
System.out.println("当前Session超时时间为[" + session.getTimeout() + "]毫秒");
session.setTimeout(1000 * 60 * 60 * 2);
System.out.println("修改Session超时时间为[" + session.getTimeout() + "]毫秒");
session.setAttribute("currentUser", value);
}
}
}
- 自定义过滤器shiroLoginFilter
package com.wang.ssm.utils.shiro;
import com.alibaba.fastjson.JSON;
import com.wang.ssm.entity.User;
import com.wang.ssm.utils.CacheUtil;
import com.wang.ssm.utils.JWT;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
//继承的实际就是authc代表的过滤器,可以查阅源码重写父类的方法实现需求
public class ShiroLoginFilter extends FormAuthenticationFilter {
private static final Logger log = LoggerFactory.getLogger(ShiroLoginFilter.class);
@Autowired
private CacheUtil cacheUtil;
/**
* 如果isAccessAllowed返回false 则执行onAccessDenied
*顾名思义,这是判断时候允许通过的方法,在这个方法里处理了options请求,
*然后接下来直接调用父类的方法实现着个方法真正需要做的事-----总结起来就是我们重写filter时
* ,如果你能力够那么你彻底覆写也没问题,
*相反,你可以先添加代码实现自己的需求,
*再调用父类同名的方法实现这个方法在shiro真正做到的功能
*
* @param request
* @param response
* @param mappedValue
* @return
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
boolean isAllowed = false;
//前端(某些框架)测试接口(OPTIONS)直接放行
if (request instanceof HttpServletRequest) {
if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
return true;
}
}
Subject subject = getSubject(request, response);
// Object principal = subject.getPrincipal();
//使用JWT的token验证,验证是否登录
// response.setCharacterEncoding("utf-8");
// HttpServletRequest request1 = (HttpServletRequest) request;
// String authorization = request1.getHeader("Authorization");
Map<String,Object> principal = (Map<String, Object>) subject.getPrincipal();
String token = (String) principal.get("token");
Object unSign = JWT.unsign(token, String.class);
if(null != unSign){
String[] split = ((String) unSign).split(",");
Object o = cacheUtil.get(split[0]);
//创建字节数组和序列化输入流,将字节数据读取到字节输入流
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(((byte[]) o));
ObjectInputStream objectInputStream = null;
try {
//将字节输入流转化为序列化输入流
objectInputStream = new ObjectInputStream(byteArrayInputStream);
//序列化成为对象
User user = (User) objectInputStream.readObject();
} catch (Exception e) {
throw new RuntimeException("缓存读取失败");
}
}
boolean permitted = subject.isPermitted("admin:ma");
return unSign!= null;
}
/**
*
* 请求未通过,肯定是没有通过shiro的认证
* 重写这个方法防止跳转到Login
* 自己看源码
*
* @param request
* @param response
* @return true-继续往下执行,false-该filter过滤器已经处理,不继续执行其他过滤器
* @throws Exception
*/
@SuppressWarnings("unchecked")
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
log.info("用户未登录");
final HttpServletRequest request2 = (HttpServletRequest) request;
final HttpServletResponse response2 = (HttpServletResponse) response;
//ajax访问接口返回数据结构
if (isAjaxRequest(request2)) {// ajax接口
//这里是个坑,如果不设置的接受的访问源,那么前端都会报跨域错误,因为这里还没到corsConfig里面
response2.setHeader("Access-Control-Allow-Origin", request2.getHeader("Origin"));
response2.setHeader("Access-Control-Allow-Credentials", "true");
response2.setCharacterEncoding("UTF-8");
response2.setContentType("application/json");
Map responseData = new HashMap();
responseData.put("state", "unauthorized");
responseData.put("code", 401);
responseData.put("msg", "用户未登录");
String result = JSON.toJSONString(responseData);
PrintWriter out;
try {
out = response2.getWriter();
out.print(result);
out.flush();
} catch (IOException e) {
log.error("返回数据失败!", e);
}
} else {
//其他情况
//shiro处理
// super.onAccessDenied(request, response);
//其他处理方式
// 页面,直接跳转登录页面
//redirect("login.html", request2, response2);
//web.xml处理
response2.setCharacterEncoding("UTF-8");
response2.setContentType("application/json");
Map responseData = new HashMap();
responseData.put("state", "unauthorized");
responseData.put("code", 1);
responseData.put("msg", "身份验证失败");
String result = JSON.toJSONString(responseData);
PrintWriter out;
try {
out = response2.getWriter();
out.print(result);
out.flush();
} catch (IOException e) {
log.error("返回数据失败!", e);
}
// response2.setContentType("application/json");
// response2.setStatus(401);// 客户试图未经授权访问受密码保护的页面。
}
return false;
}
/**
* isAjaxRequest:判断请求是否为Ajax请求. <br/>
*
* @author chenzhou
* @param request 请求对象
* @return boolean
* @since JDK 1.6
*/
public boolean isAjaxRequest(HttpServletRequest request){
String header = request.getHeader("x-requested-with, content-type,Authorization");
return "XMLHttpRequest".equals(header);
}
}
- 自定义session获取
package com.wang.ssm.utils.shiro;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
public class MySessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "cookie";
public MySessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String token = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if (!StringUtils.isBlank(token)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return token;
} else {
return super.getSessionId(request, response);
}
}
}
- 测试:usercontroller
session.getId()获取到的是当前的subject的session值,传递到前台之后,前台的请求头Authorization带上这个值
@PostMapping("/login")
public R login(@RequestBody User user) {
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());
try {
Subject subject = SecurityUtils.getSubject();
subject.login(token);
Session session = subject.getSession();
Serializable id = session.getId();
return R.ok(id);
} catch (Exception e) {
return R.failed(e.getMessage());
}
}