【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
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;
}
}
5、测试
登录:
访问其他URL:
【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成的更多相关文章
- 【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】06、Mybatis+SQLServer集成
1.增加POM依赖 注意pagehelper插件,我重写过,可以到我的这篇文章了解https://www.cnblogs.com/LiveYourLife/p/9176934.html <dep ...
- 【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】03、创建RESTful API,并统一处理返回值
本节应用Spring对RESTful的支持,使用了如@RestController等注解实现RESTful控制器. 如果对Spring中的RESTful不太明白,请查看相关书籍 1.创建一个数据对象, ...
- 【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】04、统一处理异常
本节讨论如何使用Spring的异常处理机制,当我们程序出现错误时,以相同的一种格式,把错误信息返回给客户端 1.创建一些自定义异常 public class TipsException extends ...
- 【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】01、环境准备
开发环境 windows+STS(一个针对Spring优化的Eclipse版本)+Maven+SQLServer 环境部署 1.安装SQLServer(使用版本2008R2) 自行安装,此处略过 2. ...
- 【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】02、创建新的SpringBoot项目
1.创建项目 得到项目架构 2.测试项目Web功能 默认端口为8080,运行后,输入localhost:8080/index即可访问到网页 到这里,项目构建成功!
- 使用 JSONDoc 记录 Spring Boot RESTful API
这个博文可以分为两部分:第一部分我将编写一个Spring Boot RESTful API,第二部分将介绍如何使用JSONDoc来记录创建的API.做这两个部分最多需要15分钟,因为使用Spring ...
- spring boot RESTFul API拦截 以及Filter和interceptor 、Aspect区别
今天学习一下RESTFul api拦截 大概有三种方式 一.通过Filter这个大家很熟悉了吧,这是java规范的一个过滤器,他会拦截请求.在springboot中一般有两种配置方式. 这种过滤器拦截 ...
- Spring Boot & Restful API 构建实战!
作者:liuxiaopeng https://www.cnblogs.com/paddix/p/8215245.html 在现在的开发流程中,为了最大程度实现前后端的分离,通常后端接口只提供数据接口, ...
- Spring Boot - Restful API
基本用法 @GetMapping与@PostMapping不指定参数时就是指直接使用到controller一级的url就行 @GetMapping与@PathVariable对应,前者{}中的字符串和 ...
随机推荐
- 暑期训练 CF套题
CodeForces 327A 题意:有n个数,都是0或1,然后必须执行一次操作,翻转一个区间,里面的数0变1,1变0,求最多1的数量 思路:最开始我写的最大字段和,后面好像写搓了,然后我又改成暴力, ...
- mysql启动以及常用命令汇总
mysql57的启动 常用命令 : show databases : 展示所有数据库 use 数据库名 : 连接数据库 show tables ...
- English-GIS
"Toposheet" 是 "Topographic sheet" 的简称,既地形图图幅的意思.
- SAP Smartforms打印输出条形码 及相关问题
最近凭证打印需要附加打印条形码,遂做了一个小例子,结果还出现了很多的小问题,按领导的话说,这就是经验! 首先:SE73 -> 系统条形码 -> 更改 -> 创建 -> 选择 N ...
- 重定向和转向的写法,重定向以post方式提交
重转向保留跳转过来的Referer,路径不会变1 request.getRequestDispatcher("/eventweb/index.sp?loginId=" + logi ...
- Python做个小游戏
Ps.可去知乎搜索“雨露浅歌”大神,他写的帖子里有详细讲解和源码. 游戏概述.玩法:通过键盘的↑键来控制小球往上走,当松开↑键时,小球以一定速度向下掉,小球每越过一根棒加1000分,越过一个飞镖加20 ...
- CopyOnWriteArrayList(复制数组 去实现)
一.Vector和SynchronizedList 1.1回顾线程安全的Vector和SynchronizedList 我们知道ArrayList是用于替代Vector的,Vector是线程安全的容器 ...
- 49.Kth Largest Element in an Array
Level: Medium 题目描述: Find the kth largest element in an unsorted array. Note that it is the kth lar ...
- python基础篇(文件操作)
Python基础篇(文件操作) 一.初始文件操作 使用python来读写文件是非常简单的操作. 我们使用open()函数来打开一个文件, 获取到文件句柄. 然后通过文件句柄就可以进行各种各样的操作了. ...
- 使用TortoiseGit合并分支
1.切换到主分支 2.右击选择merge, 选择被合并的分支