1、POM文件中加入Shiro和fastJSON依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.38</version> </dependency>
2、加入几个HTTP和JSON相关的工具类
package com.ltsolution.framework.util; import java.io.PrintWriter; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class LTHttpUtil { /** * 判断请求是否Ajax请求 * * @param request * @return */ public static boolean isAjax(ServletRequest request) { String header = ((HttpServletRequest) request).getHeader("x-requested-with"); if (header != null && header.equalsIgnoreCase("XMLHttpRequest")) { return true; } return false; } public static HttpServletRequest getRequest(ServletRequest request) { return new XssSqlHttpServletRequestWrapper((HttpServletRequest) request); } public static Map<String, String> getRequestHeaders(ServletRequest request) { Map<String, String> headerMap = new HashMap<>(); @SuppressWarnings("rawtypes") Enumeration enums = LTHttpUtil.getRequest(request).getHeaderNames(); while (enums.hasMoreElements()) { String name = (String) enums.nextElement(); String value = LTHttpUtil.getRequest(request).getHeader(name); if (null != value && !"".equals(value)) { headerMap.put(name, value); } } return headerMap; } /** * 读取ServletRequest请求的正文 * * @param request * @return */ @SuppressWarnings("unchecked") public static Map<String, String> getRequestBodyMap(ServletRequest request) { // 通过ServletRequest.getInputStream()可以获取到请求的正文 // 然后放置到请求的body变量中,方便在该请求的生命周期中使用,也避免多次读取 Map<String, String> dataMap = new HashMap<>(); try { if (request.getAttribute("body") != null) { dataMap = (Map<String, String>) request.getAttribute("body"); } else { // 原因https://www.cnblogs.com/wtstengshen/p/3186530.html // 因为ServletRequest的InputStream只能读取一次,所以登录的时候,这里读取了,登录方法就不能使用@RequestBody了 ServletInputStream steam = request.getInputStream(); Map<String, String> maps = LTJSON.parseObject(steam, Map.class); dataMap.putAll(maps); System.out.println(dataMap); request.setAttribute("body", dataMap); } } catch (Exception e) { System.out.println(e.getMessage()); } return dataMap; } /** * 如果是POST请求,且URL地址结尾是/login(不分大小写),则返回true * * @param request * @return */ public static boolean isLoginPost(ServletRequest request) { // 然后放置到请求的isLoginPost变量中,方便在该请求的生命周期中使用,也避免多次读取 boolean isLoginPost = false; if (request.getAttribute("isLoginPost") != null) { isLoginPost = (boolean) request.getAttribute("isLoginPost"); } else { HttpServletRequest httpRequest = (HttpServletRequest) request; String url = httpRequest.getRequestURL().toString(); url = url.substring(url.lastIndexOf("/") + 1, url.length()).toUpperCase(); String method = httpRequest.getMethod().toUpperCase(); if (url.equals("LOGIN") && method.equals("POST")) isLoginPost = true; request.setAttribute("isLoginPost", isLoginPost); } return isLoginPost; } /** * 将过滤器中产生的异常以Json的形式返回给客户端 * * @param response * @param obj */ public static void ResponseWrite(ServletResponse response, Object obj) { PrintWriter out = null; try { HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setStatus(401); httpResponse.setCharacterEncoding("UTF-8"); httpResponse.setContentType("application/json"); out = httpResponse.getWriter(); out.println(LTJSON.toJSONString(obj)); } catch (Exception e) { System.out.println(e); } finally { if (null != out) { out.flush(); out.close(); } } } }
package com.ltsolution.framework.util; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Type; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.serializer.SerializerFeature; /** * @author Yang * @description 用于对JSON操作做一个封装,方便以后更换修改 */ public class LTJSON { /** * 对象转换为JSON * * @param object * @return */ public static final String toJSONString(Object object) { /* * QuoteFieldNames———-输出key时是否使用双引号,默认为true * WriteMapNullValue——–是否输出值为null的字段,默认为false * WriteNullNumberAsZero—-数值字段如果为null,输出为0,而非null * WriteNullListAsEmpty—–List字段如果为null,输出为[],而非null * WriteNullStringAsEmpty—字符类型字段如果为null,输出为"",而非null * WriteNullBooleanAsFalse–Boolean字段如果为null,输出为false,而非null */ return JSONObject.toJSONString(object, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.WriteNullNumberAsZero, SerializerFeature.WriteNullListAsEmpty, SerializerFeature.WriteNullBooleanAsFalse); } /** * 输入流转换为对象 * * @param is * @param type * @param features * @return * @throws IOException */ @SuppressWarnings("unchecked") public static <T> T parseObject(InputStream is, // Type type, // Feature... features) throws IOException { T parseObject = (T) JSON.parseObject(is, type, features); return parseObject; } }
package com.ltsolution.framework.util; import java.util.HashMap; import java.util.Map; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /* * * @Author tomsun28 * @Description request请求安全过滤包装类 * @Date 20:41 2018/4/15 */ public class XssSqlHttpServletRequestWrapper extends HttpServletRequestWrapper { public XssSqlHttpServletRequestWrapper(HttpServletRequest request) { super(request); } /* * * @Description 重写 数组参数过滤 * @Param [parameter] * @Return java.lang.String[] */ @Override public String[] getParameterValues(String parameter) { String[] values = super.getParameterValues(parameter); if (values == null) { return null; } int count = values.length; String[] encodedValues = new String[count]; for (int i = 0 ; i < count ; i++ ) { encodedValues[i] = filterParamString(values[i]); } return encodedValues; } @Override public Map<String,String[]> getParameterMap() { Map<String,String[]> primary = super.getParameterMap(); Map<String,String[]> result = new HashMap<>(); for (Map.Entry<String,String[]> entry : primary.entrySet()) { result.put(entry.getKey(),filterEntryString(entry.getValue())); } return result; } @Override public String getParameter(String parameter) { return filterParamString(super.getParameter(parameter)); } @Override public String getHeader(String name) { return filterParamString(super.getHeader(name)); } @Override public Cookie[] getCookies() { Cookie[] cookies = super.getCookies(); if (cookies != null) { for (int i = 0 ; i < cookies.length; i++) { Cookie cookie = cookies[i]; cookie.setValue(filterParamString(cookie.getValue())); } } return cookies; } /* * * @Description 过滤字符串数组不安全内容 * @Param [value] * @Return java.lang.String[] */ private String[] filterEntryString(String[] value) { for (int i = 0; i < value.length; i++) { value[i] = filterParamString(value[i]); } return value; } /* * * @Description 过滤字符串不安全内容 * @Param [value] * @Return java.lang.String */ private String filterParamString(String value) { if (null == value) { return null; } // 过滤XSS 和 SQL 注入 return XssUtil.stripSqlXss(value); } }
package com.ltsolution.framework.util; import java.util.regex.Pattern; /* * * @Author tomsun28 * @Description Web防火墙工具类 * @Date 19:51 2018/4/15 */ public class XssUtil { /* * * @Description 过滤XSS脚本内容 * @Param [value] * @Return java.lang.String */ public static String stripXSS(String value) { String rlt = null; if (null != value) { // NOTE: It's highly recommended to use the ESAPI library and uncomment the following line to // avoid encoded attacks. // value = ESAPI.encoder().canonicalize(value); // Avoid null characters rlt = value.replaceAll("", ""); // Avoid anything between script tags Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE); rlt = scriptPattern.matcher(rlt).replaceAll(""); // Avoid anything in a src='...' type of expression /*scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); rlt = scriptPattern.matcher(rlt).replaceAll(""); scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); rlt = scriptPattern.matcher(rlt).replaceAll("");*/ // Remove any lonesome </script> tag scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE); rlt = scriptPattern.matcher(rlt).replaceAll(""); // Remove any lonesome <script ...> tag scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); rlt = scriptPattern.matcher(rlt).replaceAll(""); // Avoid eval(...) expressions scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); rlt = scriptPattern.matcher(rlt).replaceAll(""); // Avoid expression(...) expressions scriptPattern = Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); rlt = scriptPattern.matcher(rlt).replaceAll(""); // Avoid javascript:... expressions scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE); rlt = scriptPattern.matcher(rlt).replaceAll(""); // Avoid vbscript:... expressions scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE); rlt = scriptPattern.matcher(rlt).replaceAll(""); // Avoid onload= expressions scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); rlt = scriptPattern.matcher(rlt).replaceAll(""); } return rlt; } /* * * @Description 过滤SQL注入内容 * @Param [value] * @Return java.lang.String */ public static String stripSqlInjection(String value) { return (null == value) ? null : value.replaceAll("('.+--)|(--)|(%7C)", ""); //value.replaceAll("('.+--)|(--)|(\\|)|(%7C)", ""); } /* * * @Description 过滤SQL 和 XSS注入内容 * @Param [value] * @Return java.lang.String */ public static String stripSqlXss(String value) { return stripXSS(stripSqlInjection(value)); } }
package com.ltsolution.framework.util; import java.util.UUID; public class LTUUID { public static String getUUID32(){ String uuid = UUID.randomUUID().toString().replace("-", "").toLowerCase(); return uuid; } }
3、创建Shiro所需的一些基础类
a、Shiro用户模型
简单的一个用户对象,避免Shiro与数据库用户实体耦合
public class ShiroUser { private String username; private String password; public ShiroUser() { } public ShiroUser(String username, String password) { this.username = username; this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
b、Shiro Token令牌
在Shiro中,处理认证时,一般是使用Token令牌,传送给Realm来进行处理
用户名密码令牌
public class MyUsernamePasswordToken extends UsernamePasswordToken { private static final long serialVersionUID = 8505830411513785701L; public MyUsernamePasswordToken(String username, String pswd) { super(username, pswd); this.pswd = pswd; } private String pswd; public String getPswd() { return pswd; } public void setPswd(String pswd) { this.pswd = pswd; } }
Web Token令牌:因为是RESTful风格的框架,在用户登录后会生成一个用户凭证,用户访问其它功能时,每次都要把这个凭证带上,才能知道他是该用户,这个Web Token令牌是用来处理这种凭证的
package com.ltsolution.framework.shiro.token; import org.apache.shiro.authc.AuthenticationToken; /* * * @Author tomsun28 * @Description JWT token * @Date 19:37 2018/2/10 */ public class MyWebJsonToken implements AuthenticationToken { private static final long serialVersionUID = 492511894487921685L; private String username; // 用户名 private String userip; // 用户IP private String userdevice; // 用户设备信息 private String usertoken; // 用户json web token值 public MyWebJsonToken(String ip, String deivce, String token, String username) { this.userip = ip; this.userdevice = deivce; this.usertoken = token; this.username = username; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getUserip() { return userip; } public void setUserip(String userip) { this.userip = userip; } public String getUserdevice() { return userdevice; } public void setUserdevice(String userdevice) { this.userdevice = userdevice; } public String getUsertoken() { return usertoken; } public void setUsertoken(String usertoken) { this.usertoken = usertoken; } @Override public Object getPrincipal() { return username; } @Override public Object getCredentials() { return usertoken; } }
c、用户缓存
当一个用户登录后,会用户名和凭证缓存下来,那么用户带上凭证访问其它功能时,就能判断该用户是否已经登录
这里使用一个静态变量来简化处理,其它更好的方式可以自行改造(如使用redis缓存等)
package com.ltsolution.framework.shiro.cache; import java.util.HashMap; import java.util.Map; //应该使用接口管理Shiro处理数据的部分 public class MyShiroCache { // 已登录的用户 private static Map<String, String> onlineUsers = new HashMap<String, String>(); public static Map<String, String> getOnlineUsers() { return onlineUsers; } public static void setOnlineUsers(Map<String, String> onlineUsers) { MyShiroCache.onlineUsers = onlineUsers; } public static String getUserNameByToken(String userToken) { for (String key : onlineUsers.keySet()) { System.out.println("key:"+key); System.out.println("tokken:"+onlineUsers.get(key)); if (onlineUsers.get(key).equals(userToken)) return key; } return null; } }
d、数据服务
判断一个用户是否帐号密码正确,或者用户是否有权限访问某个功能
这里定义几个接口,实现的话,我这里是随便写了几个假定实现,请自行完善
数据模型
package com.ltsolution.framework.bs.system.model; public class User { private String username; private String password; public User(String username, String password) { this.username = username; this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
数据接口
package com.ltsolution.framework.bs.system.service; import com.ltsolution.framework.bs.system.model.User; public interface UserService { public User getUserByUserName(String username); public boolean isPermitted(String url, String method, String username); }
数据接口实现
package com.ltsolution.framework.bs.system.service.impl; import org.springframework.stereotype.Service; import com.ltsolution.framework.bs.system.model.User; import com.ltsolution.framework.bs.system.service.UserService; @Service public class UserServiceImpl implements UserService { public User getUserByUserName(String username) { if (username != null && username.equals("yang")) return new User(username, "1234"); else return null; } @Override public boolean isPermitted(String url, String method, String username) { System.out.println("【UserServiceImpl】url:" + url); System.out.println("【UserServiceImpl】method:" + method); System.out.println("【UserServiceImpl】username:" + username); // 根据用户,找角色,找权限,然后找到这些权限中,第一个斜杠前和url相等,且斜杠数量相同的部分 // 然后循环这些url // --去掉url中的/*,然后和访问的url对比 // --找到能匹配和方法相同的,就代表有权限了 return false; } }
shiro接口
package com.ltsolution.framework.shiro.service; import com.ltsolution.framework.shiro.model.ShiroUser; public interface ShiroAuthcService { public ShiroUser getShiroUserByName(String username); public ShiroUser getShiroUserByToken(String token); }
package com.ltsolution.framework.shiro.service; public interface ShiroAuthorService { public boolean isPermitted(String url, String method, String username); }
shiro实现
package com.ltsolution.framework.shiro.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ltsolution.framework.bs.system.model.User; import com.ltsolution.framework.bs.system.service.UserService; import com.ltsolution.framework.shiro.cache.MyShiroCache; import com.ltsolution.framework.shiro.model.ShiroUser; import com.ltsolution.framework.shiro.service.ShiroAuthcService; @Service public class ShiroAuthcServiceImpl implements ShiroAuthcService { @Autowired UserService userService; public ShiroUser getShiroUserByName(String username) { User user = userService.getUserByUserName(username); if (user != null) { ShiroUser shiroUser = new ShiroUser(); shiroUser.setUsername(user.getUsername()); shiroUser.setPassword(user.getPassword()); return shiroUser; } else return null; } @Override public ShiroUser getShiroUserByToken(String token) { String username = MyShiroCache.getUserNameByToken(token); return getShiroUserByName(username); } }
package com.ltsolution.framework.shiro.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ltsolution.framework.bs.system.service.UserService; import com.ltsolution.framework.shiro.service.ShiroAuthorService; @Service public class ShiroAuthorServiceImpl implements ShiroAuthorService { @Autowired UserService userService; public boolean isPermitted(String url, String method, String username) { return userService.isPermitted(url, method, username); } }
4、集成Shiro的思路
这里没有使用Shiro的一般做法,所以大家自行权宜此方案是否合适
思路:
a、创建2个过滤器,分别为:登录认证过滤器,授权过滤器
b、设置登录方法需要经过登录认证过滤器,其它方法需要经过授权过滤器
c、当登录方法经过登录认证过滤器时,进入登录认证Realm处理,如果确认帐号密码成功,则返回用户的凭证到前台;否则直接返回错误信息到前台。其实这里这样处理的话,连登录控制器都不需要,直接在过滤器中返回数据即可。
d、当前台带凭证访问后台URL时,会被权限过滤器所截获,然后先判断这个凭证是否有效;有效,则访问数据库查看该用户是否有此URL的权限,如果有权限,则进入控制器方法;如果凭证无效或者没有权限,则直接返回错误信息。
Realm设计:2个Realm,一个用于认证,一个用于授权
package com.ltsolution.framework.shiro.realm; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.CredentialsException; import org.apache.shiro.authc.credential.SimpleCredentialsMatcher; import com.ltsolution.framework.shiro.token.MyUsernamePasswordToken; public class AuthcRealmMatcher extends SimpleCredentialsMatcher { @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { System.out.println("启动密码对比器。"); MyUsernamePasswordToken mytoken = (MyUsernamePasswordToken) token; System.out.println(mytoken.getPswd()); if (mytoken.getPswd() != null && mytoken.getPswd().equals((String)info.getCredentials())) { System.out.println("密码对比器对比成功。"); return true; } else { throw new CredentialsException(); } } }
package com.ltsolution.framework.shiro.realm; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; 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 org.springframework.stereotype.Component; import com.ltsolution.framework.shiro.model.ShiroUser; import com.ltsolution.framework.shiro.service.ShiroAuthcService; import com.ltsolution.framework.shiro.token.MyUsernamePasswordToken; @Component public class AuthcRealm extends AuthorizingRealm { @Autowired ShiroAuthcService shiroAuthcService; public AuthcRealm() { super(); this.setCredentialsMatcher(new AuthcRealmMatcher()); } @Override public String getName() { return "authcRealm"; } @Override public boolean supports(AuthenticationToken token) { return token instanceof MyUsernamePasswordToken; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) { System.out.println("检查用户的是否已认证!"); MyUsernamePasswordToken token = (MyUsernamePasswordToken) authcToken; String username = token.getUsername(); System.out.println("userName:" + username); String password = token.getPswd(); System.out.println("password:" + password); ShiroUser user = shiroAuthcService.getShiroUserByName(username); if (user == null) { System.out.println("抛出异常UnknownAccountException"); throw new UnknownAccountException(); } // //可以更新用户登录时间等操作 // System.out.println("进行用户登录时间更新等操作"); // 返回一个认证信息,代表认证成功 System.out.println("Realm认证成功!"); return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), this.getName()); } /** * 授权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); return info; } }
package com.ltsolution.framework.shiro.realm; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.credential.SimpleCredentialsMatcher; public class AuthorRealmMatcher extends SimpleCredentialsMatcher { @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { //直接return true,代表授权过滤器,不需要realm的密码对比器来控制认证 return true; } }
package com.ltsolution.framework.shiro.realm; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; 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 org.springframework.stereotype.Component; import com.ltsolution.framework.shiro.cache.MyShiroCache; import com.ltsolution.framework.shiro.model.ShiroUser; import com.ltsolution.framework.shiro.service.ShiroAuthcService; import com.ltsolution.framework.shiro.token.MyWebJsonToken; @Component public class AuthorRealm extends AuthorizingRealm { @Autowired ShiroAuthcService shiroAuthcService; public AuthorRealm() { super(); this.setCredentialsMatcher(new AuthorRealmMatcher()); } @Override public String getName() { return "authorRealm"; } @Override public boolean supports(AuthenticationToken token) { return token instanceof MyWebJsonToken; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) { System.out.println("检查用户的是否已认证!"); MyWebJsonToken myWebJsonToken = (MyWebJsonToken) authcToken; String userToken = myWebJsonToken.getUsertoken(); System.out.println("usertoken:" + userToken); String userName = MyShiroCache.getUserNameByToken(userToken); System.out.println("MyShiroCache:" + MyShiroCache.getOnlineUsers()); System.out.println("userName:" + userName); ShiroUser user = shiroAuthcService.getShiroUserByName(userName); if (user == null) { System.out.println("找不到帐号"); throw new UnknownAccountException(); } // //可以更新用户登录时间等操作 // System.out.println("进行用户登录时间更新等操作"); // 返回一个认证信息,代表认证成功 System.out.println("Realm认证成功!"); return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), this.getName()); } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); return info; } }
过滤器设计:2个过滤器,一个用于登录URL,一个用于其他URL检测授权
package com.ltsolution.framework.shiro.filter; import java.util.Map; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.shiro.authc.CredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.AccessControlFilter; import com.ltsolution.framework.common.msgmodel.AppResult; import com.ltsolution.framework.shiro.cache.MyShiroCache; import com.ltsolution.framework.shiro.token.MyUsernamePasswordToken; import com.ltsolution.framework.util.LTHttpUtil; import com.ltsolution.framework.util.LTUUID; public class AuthcFilter extends AccessControlFilter { final static Class<AuthcFilter> CLASS = AuthcFilter.class; @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { // isAccessAllowed 此方法用于判断访问是否允许 // return true 允许访问→进入控制器方法 // return false 不允许访问→进入onAccessDenied方法 System.out.println("认证过滤器"); // Subject subject = getSubject(request, response); // if(subject) // return subject.isAuthenticated(); return false; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { // onAccessDenied 此方法二次判断访问是否允许 // return true 允许访问→进入控制器方法 // return false 不允许访问→返回空响应 System.out.println("认证过滤器失败处理"); // 如果是登录操作,则进行登录认证 // 认证成功则返回true,进入登录控制器 // 认证失败则返回失败信息的响应 if (LTHttpUtil.isLoginPost(request)) { // 获取Shiro用户 Subject subject = getSubject(request, response); try { // 获取请求传输的帐号密码 Map<String, String> bodyMap = LTHttpUtil.getRequestBodyMap(request); String username = bodyMap.get("username"); String password = bodyMap.get("password"); // 生成一个令牌,进行登录认证 MyUsernamePasswordToken token = new MyUsernamePasswordToken(username, password); // 调用login方法时,Shiro会调用配置好的Realm的doGetAuthenticationInfo方法进行认证 subject.login(token); //登录成功,创建一个WebToken String uuid = LTUUID.getUUID32(); MyShiroCache.getOnlineUsers().put(username,uuid ); LTHttpUtil.ResponseWrite(response, new AppResult().ok("登录成功!").addData("token", uuid)); // 如果Realm的doGetAuthenticationInfo方法认证成功,则返回true,进入控制器方法 System.out.println("过滤器登录成功!"); return false; // 如果Realm的doGetAuthenticationInfo方法认证失败,会抛出异常 // 我们对异常进行截获,然后在响应信息中加入需要提示的数据,并返回false } catch (UnknownAccountException e) { // 当使用多个Realm的时候吗,当一个Reaml抛出以上时会继续下个Realm,最终抛出AuthenticationException,而Realm内部的异常截获不到 System.out.println("Authc过滤器截获UnknownAccountException异常!"); LTHttpUtil.ResponseWrite(response, new AppResult().error("账号不存在!")); return false; } catch (CredentialsException e) { // 当使用多个Realm的时候吗,当一个Reaml抛出以上时会继续下个Realm,最终抛出AuthenticationException,而Realm内部的异常截获不到 System.out.println("Authc过滤器截获CredentialsException异常!"); LTHttpUtil.ResponseWrite(response, new AppResult().error("密码错误!")); return false; } catch (Exception e) { System.out.println("Authc过滤器截获" + e.getClass().getTypeName() + "异常!"); LTHttpUtil.ResponseWrite(response, new AppResult().error(e.getClass().getTypeName() + " " + e.getMessage())); return false; } } return false; } }
package com.ltsolution.framework.shiro.filter; import java.util.Map; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.AccessControlFilter; import org.apache.shiro.web.util.WebUtils; import com.ltsolution.framework.common.msgmodel.AppResult; import com.ltsolution.framework.shiro.service.ShiroAuthorService; import com.ltsolution.framework.shiro.token.MyWebJsonToken; import com.ltsolution.framework.util.LTHttpUtil; public class AuthorFilter extends AccessControlFilter { final static Class<AuthorFilter> CLASS = AuthorFilter.class; private ShiroAuthorService shiroAuthorService; @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { // isAccessAllowed 此方法用于判断访问是否允许 // return true 允许访问→进入控制器方法 // return false 不允许访问→进入onAccessDenied方法 System.out.println("权限过滤器"); // Subject subject = getSubject(request, response); // if(subject) // return subject.isAuthenticated(); return false; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { // onAccessDenied 此方法二次判断访问是否允许 // return true 允许访问→进入控制器方法 // return false 不允许访问→返回空响应 System.out.println("权限过滤器失败处理"); // 检查当前token是否有对应的已登录用户 Subject subject = getSubject(request, response); try { // --获取json token Map<String, String> maps = LTHttpUtil.getRequestHeaders(request); // 获取请求头部所带的信息,主要是token String userip = request.getRemoteAddr(); String userdevice = maps.get("userdevice"); String usertoken = maps.get("usertoken"); String username = maps.get("usaername"); // 生成一个令牌,进行登录认证 MyWebJsonToken token = new MyWebJsonToken(userip, userdevice, usertoken, username); // 调用login方法时,Shiro会调用配置好的Realm的doGetAuthenticationInfo方法进行认证 subject.login(token); // 如果Realm的doGetAuthenticationInfo方法认证成功,则返回true,进入控制器方法 System.out.println("过滤器登录成功!"); // return true; // 如果Realm的doGetAuthenticationInfo方法认证失败,会抛出异常 } catch (UnknownAccountException ex) { System.out.println("Author过滤器截获UnknownAccountException异常!"); LTHttpUtil.ResponseWrite(response, new AppResult().error("用户未登录!")); return false; } catch (Exception ex) { System.out.println("Author过滤器截获Exception异常!"); LTHttpUtil.ResponseWrite(response, new AppResult().error(ex.getMessage())); return false; } // 判断是否授权 String url = this.getPathWithinApplication(request).toLowerCase(); System.out.println("【AuthorFilter】url:" + url); String method = WebUtils.toHttp(request).getMethod().toUpperCase(); System.out.println("【AuthorFilter】method:" + method); String principal = subject.getPrincipal().toString(); System.out.println("【AuthorFilter】principal:" + principal); System.out.println("【AuthorFilter】shiroAuthorService:" + shiroAuthorService); if (subject == null || !shiroAuthorService.isPermitted(url, method, principal)) { System.out.println("【AuthorFilter】发现用户未授权!"); LTHttpUtil.ResponseWrite(response, new AppResult().error("当前用户没有此权限!")); return false; } System.out.println("【AuthorFilter】发现用户已授权!"); // 过滤成功,进入控制器 return true; } public void setShiroAuthorService(ShiroAuthorService shiroAuthorService) { this.shiroAuthorService = shiroAuthorService; } }
最后就是设置Shiro的配置类
package com.ltsolution.framework.shiro.config; import java.util.Collection; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.pam.AuthenticationStrategy; import org.apache.shiro.authc.pam.ModularRealmAuthenticator; import org.apache.shiro.realm.Realm; /* * * @Author tomsun28 * @Description * @Date 21:15 2018/3/3 */ public class MyModularRealmAuthenticator extends ModularRealmAuthenticator { @Override //原方法中没有抛出异常,这里改成抛出异常 protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) { AuthenticationStrategy strategy = getAuthenticationStrategy(); AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token); for (Realm realm : realms) { aggregate = strategy.beforeAttempt(realm, token, aggregate); if (realm.supports(token)) { AuthenticationInfo info = null; Throwable t = null; info = realm.getAuthenticationInfo(token); aggregate = strategy.afterAttempt(realm, token, info, aggregate, t); } else { } } aggregate = strategy.afterAllAttempts(token, aggregate); return aggregate; } }
package com.ltsolution.framework.shiro.config; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import javax.servlet.Filter; import org.apache.shiro.authc.pam.ModularRealmAuthenticator; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.Realm; 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 com.ltsolution.framework.shiro.filter.AuthcFilter; import com.ltsolution.framework.shiro.filter.AuthorFilter; import com.ltsolution.framework.shiro.realm.AuthcRealm; import com.ltsolution.framework.shiro.realm.AuthorRealm; import com.ltsolution.framework.shiro.service.ShiroAuthorService; @Configuration public class ShiroConfiguration { @Autowired AuthcRealm authcRealm; @Autowired AuthorRealm authorRealm; @Autowired ShiroAuthorService shiroAuthorService; @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 修改了一下验证器,在多Realm下正常处理自定义异常 // 必须放在setRealms前面,不然无效 ModularRealmAuthenticator authenticator = new MyModularRealmAuthenticator(); securityManager.setAuthenticator(authenticator); Collection<Realm> realms = new ArrayList<Realm>(); realms.add(authcRealm); realms.add(authorRealm); securityManager.setRealms(realms); // securityManager.setRealm(authcRealm); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // 添加自定义过滤器,用于下面过滤器链的配置 Map<String, Filter> filterMap = shiroFilterFactoryBean.getFilters(); filterMap.put("LTAuthc", new AuthcFilter()); AuthorFilter LTAuthorFilter = new AuthorFilter(); LTAuthorFilter.setShiroAuthorService(shiroAuthorService); filterMap.put("LTAuthor", LTAuthorFilter); shiroFilterFactoryBean.setFilters(filterMap); // 配置过滤器链 // 使用LinkedHashMap,是一个有序列表,判断一个URL的过滤器时,只找符合条件的第一个过滤器,后面的过滤器都忽略 // 还要注意的一点是,put方法不要设置相同的Key,因为会替代前面相同的Key,使前面相同的Key无效,且影响阅读和理解 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // /**放到最后,如果放第一,那么所有URL的过滤器都是找到这个过滤器了,后面的过滤器都失效 // 标明所有功能都需要认证才能登陆 // 登录方法,通过认证条件:帐号密码符合要求;返回值是token filterChainDefinitionMap.put("/login", "LTAuthc"); // 其它页面,需要授权:根据请求头部的token信息,查找相应的权限并对比 filterChainDefinitionMap.put("/**", "LTAuthor"); // 本系统基于REST,所以不需要返回页面 // // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 // shiroFilterFactoryBean.setLoginUrl("/login"); // // 登录成功后要跳转的链接 // shiroFilterFactoryBean.setSuccessUrl("/index"); // // 未授权界面; // shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } }