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集成的更多相关文章

  1. 【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】06、Mybatis+SQLServer集成

    1.增加POM依赖 注意pagehelper插件,我重写过,可以到我的这篇文章了解https://www.cnblogs.com/LiveYourLife/p/9176934.html <dep ...

  2. 【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】03、创建RESTful API,并统一处理返回值

    本节应用Spring对RESTful的支持,使用了如@RestController等注解实现RESTful控制器. 如果对Spring中的RESTful不太明白,请查看相关书籍 1.创建一个数据对象, ...

  3. 【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】04、统一处理异常

    本节讨论如何使用Spring的异常处理机制,当我们程序出现错误时,以相同的一种格式,把错误信息返回给客户端 1.创建一些自定义异常 public class TipsException extends ...

  4. 【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】01、环境准备

    开发环境 windows+STS(一个针对Spring优化的Eclipse版本)+Maven+SQLServer 环境部署 1.安装SQLServer(使用版本2008R2) 自行安装,此处略过 2. ...

  5. 【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】02、创建新的SpringBoot项目

    1.创建项目 得到项目架构 2.测试项目Web功能 默认端口为8080,运行后,输入localhost:8080/index即可访问到网页 到这里,项目构建成功!

  6. 使用 JSONDoc 记录 Spring Boot RESTful API

    这个博文可以分为两部分:第一部分我将编写一个Spring Boot RESTful API,第二部分将介绍如何使用JSONDoc来记录创建的API.做这两个部分最多需要15分钟,因为使用Spring ...

  7. spring boot RESTFul API拦截 以及Filter和interceptor 、Aspect区别

    今天学习一下RESTFul api拦截 大概有三种方式 一.通过Filter这个大家很熟悉了吧,这是java规范的一个过滤器,他会拦截请求.在springboot中一般有两种配置方式. 这种过滤器拦截 ...

  8. Spring Boot & Restful API 构建实战!

    作者:liuxiaopeng https://www.cnblogs.com/paddix/p/8215245.html 在现在的开发流程中,为了最大程度实现前后端的分离,通常后端接口只提供数据接口, ...

  9. Spring Boot - Restful API

    基本用法 @GetMapping与@PostMapping不指定参数时就是指直接使用到controller一级的url就行 @GetMapping与@PathVariable对应,前者{}中的字符串和 ...

随机推荐

  1. 暑期训练 CF套题

    CodeForces 327A 题意:有n个数,都是0或1,然后必须执行一次操作,翻转一个区间,里面的数0变1,1变0,求最多1的数量 思路:最开始我写的最大字段和,后面好像写搓了,然后我又改成暴力, ...

  2. mysql启动以及常用命令汇总

    mysql57的启动 常用命令 : show databases        :            展示所有数据库 use  数据库名      :     连接数据库 show tables ...

  3. English-GIS

    "Toposheet" 是 "Topographic sheet" 的简称,既地形图图幅的意思.

  4. SAP Smartforms打印输出条形码 及相关问题

    最近凭证打印需要附加打印条形码,遂做了一个小例子,结果还出现了很多的小问题,按领导的话说,这就是经验! 首先:SE73 -> 系统条形码 -> 更改 -> 创建 -> 选择 N ...

  5. 重定向和转向的写法,重定向以post方式提交

    重转向保留跳转过来的Referer,路径不会变1 request.getRequestDispatcher("/eventweb/index.sp?loginId=" + logi ...

  6. Python做个小游戏

    Ps.可去知乎搜索“雨露浅歌”大神,他写的帖子里有详细讲解和源码. 游戏概述.玩法:通过键盘的↑键来控制小球往上走,当松开↑键时,小球以一定速度向下掉,小球每越过一根棒加1000分,越过一个飞镖加20 ...

  7. CopyOnWriteArrayList(复制数组 去实现)

    一.Vector和SynchronizedList 1.1回顾线程安全的Vector和SynchronizedList 我们知道ArrayList是用于替代Vector的,Vector是线程安全的容器 ...

  8. 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 ...

  9. python基础篇(文件操作)

    Python基础篇(文件操作) 一.初始文件操作 使用python来读写文件是非常简单的操作. 我们使用open()函数来打开一个文件, 获取到文件句柄. 然后通过文件句柄就可以进行各种各样的操作了. ...

  10. 使用TortoiseGit合并分支

    1.切换到主分支 2.右击选择merge, 选择被合并的分支