權(quán)限類說明:
權(quán)限配置類:ShiroConfiguration (權(quán)限核心配置類)叽讳、AuthFilter(攔截器配置類)、AuthRealm(認證類)蝠咆、AuthTokenVo冰更、NoPermissionException(無權(quán)限異常處理類)、JWTUtil(token工具類)贱勃、AuthConstant(提示類)井赌、ReturnMessage 返回信息類
測試需要的類:User UserController UserList UserPermission UserRole
maven坐標
權(quán)限核心配置
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @ClassName ShiroConfiguration
* @Description
* @Author XinChunYu
* @Date 2020/5/29 13:42
* @Version 1.0
**/
@Configuration
public class ShiroConfiguration {
private org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(ShiroConfiguration.class);
//從配置文件里面讀取是否需要啟動登錄認證的開關,默認true
//@Value("${jwt.auth}")
private boolean auth = true;
//配置攔截器
@Bean
public ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//設置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
//啟用認證
String openAuth = auth ? "auth" : "anon";
//自定義過濾器鏈
Map<String, javax.servlet.Filter> filters = new HashMap<>();
//指定攔截器處理
filters.put("auth", new com.lzqs.yuanzilian.shiro.AuthFilter());
shiroFilterFactoryBean.setFilters(filters);
Map<String, String> filterMap = new LinkedHashMap<>();
//登錄請求不攔截
filterMap.put("/user/login", "anon");
//登錄頁面需要用到的接口仇穗,不攔截
filterMap.put("/user/fetchCurrentUser", "anon");
//攔截所有接口請求戚绕,做權(quán)限判斷
filterMap.put("/**", openAuth);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
logger.info("Shiro攔截器工廠類注入成功");
return shiroFilterFactoryBean;
}
// SecurityManager 安全管理器;Shiro的核心
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
return securityManager;
}
//自定義身份認證realm
@Bean
public AuthRealm userRealm() {
return new AuthRealm();
}
@Bean("lifecycleBeanPostProcessor")
//管理shiro生命周期
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
//Shiro注解支持
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager((org.apache.shiro.mgt.SecurityManager) securityManager);
return authorizationAttributeSourceAdvisor;
}
}
權(quán)限過濾器
package com.lzqs.yuanzilian.shiro;
import com.alibaba.fastjson.JSONObject;
import com.lzqs.yuanzilian.constant.ReturnMessage;
import com.lzqs.yuanzilian.shiro.util.AuthConstant;
import com.lzqs.yuanzilian.shiro.util.JWTUtil;
import io.micrometer.core.instrument.util.StringUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @ClassName AuthFilter
* @Description 實現(xiàn)自定義的認證攔截器,接收傳過來的token,實現(xiàn)前后端分離的權(quán)限認證
* @Author XinChunYu
* @Date 2020/5/29 14:03
* @Version 1.0
**/
public class AuthFilter extends AuthenticatingFilter {
private org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(AuthorizingRealm.class);
private ReturnMessage responseResult = ReturnMessage.failWithMsg(AuthConstant.AUTHENTICATE_FAIL);
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
return null;
}
/**
* 在這里攔截所有請求
* @param request
* @param response
* @param mappedValue
* @return
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
String token = JWTUtil.getRequestToken((HttpServletRequest)request);
if (!StringUtils.isBlank(token)){
try {
this.executeLogin(request, response);
} catch (Exception e) {
// 應用異常
logger.info(e.getMessage());
responseResult = ReturnMessage.failWithMsg(e.getMessage());
return false;
}
} else {
// cookie中未檢查到token或token為空
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
String httpMethod = httpServletRequest.getMethod();
String requestURI = httpServletRequest.getRequestURI();
responseResult = ReturnMessage.failWithMsg(AuthConstant.TOKEN_BLANK);
logger.info("請求 {} 的Token為空 請求類型 {}", requestURI, httpMethod);
return false;
}
return true;
}
/**
* 請求失敗攔截,請求終止,不進行轉(zhuǎn)發(fā)直接返回客戶端攔截結(jié)果
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception{
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
httpServletResponse.setContentType("application/json; charset=utf-8");
httpServletResponse.setCharacterEncoding("UTF-8");
//ReturnMessage returnMessage = ReturnMessage.failWithMsg(AuthConstant.AUTHENTICATE_FAIL);
httpServletResponse.getWriter().print(JSONObject.toJSONString(responseResult));
return false;
}
/**
* 用戶存在谷誓,執(zhí)行登錄認證
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
String token = JWTUtil.getRequestToken((HttpServletRequest)request);
AuthTokenVo jwtToken = new AuthTokenVo(token);
// 提交給AuthRealm進行登錄認證
getSubject(request, response).login(jwtToken);
return true;
}
}
token認證和權(quán)限認證
import com.lzqs.yuanzilian.shiro.controller.User;
import com.lzqs.yuanzilian.shiro.controller.UserList;
import com.lzqs.yuanzilian.shiro.controller.UserPermission;
import com.lzqs.yuanzilian.shiro.controller.UserRole;
import com.lzqs.yuanzilian.shiro.util.AuthConstant;
import io.micrometer.core.instrument.util.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
/**
* @ClassName AuthRealm
* @Description
* @Author XinChunYu
* @Date 2020/5/29 13:51
* @Version 1.0
**/
public class AuthRealm extends AuthorizingRealm {
private org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(AuthorizingRealm.class);
/**
* 重寫懊烤,繞過身份令牌異常導致的shiro報錯
* @param authenticationToken
* @return
*/
@Override
public boolean supports(AuthenticationToken authenticationToken){
return authenticationToken instanceof AuthTokenVo;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("用戶角色權(quán)限認證");
//獲取用戶登錄信息
User user = (User)principals.getPrimaryPrincipal();
//添加角色和權(quán)限
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
for(UserRole userRole : user.getUserRoleList()){
authorizationInfo.addRole(userRole.getUserRoleName());
for(UserPermission permissionVo : userRole.getUserPermissionList()){
authorizationInfo.addStringPermission(permissionVo.getUserPermissionName());
}
}
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
logger.info("執(zhí)行認證邏輯");
//獲得token
String token = (String)authenticationToken.getCredentials();
//獲得token中的用戶信息
String username = JwtAuthenticator.getUsername(token);
//判空
if(StringUtils.isBlank(username)){
throw new AuthenticationException(AuthConstant.TOKEN_BLANK);
}
User user = null;
try{
for(User u : UserList.userList){
if(u.getUsername().equals(username)){
user = u;
}
}
//查詢用戶是否存在
if(user == null){
throw new AuthenticationException(AuthConstant.TOKEN_INVALID);
//token過期
}else if(!(JwtAuthenticator.verifyToken(token, username, user.getPassword()))){
throw new AuthenticationException(AuthConstant.TOKEN_EXPIRE);
}
}catch (Exception e){
throw e;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user,
token,
getName());
return authenticationInfo;
}
}
jwt token對象
import org.apache.shiro.authc.AuthenticationToken;
/**
* @ClassName AuthTokenVo
* @Description
* @Author XinChunYu
* @Date 2020/5/29 14:09
* @Version 1.0
**/
public class AuthTokenVo implements AuthenticationToken {
private String token;
public AuthTokenVo(String token){
this.token = token;
}
@Override
public Object getPrincipal(){
return token;
}
@Override
public Object getCredentials(){
return token;
}
}
異常處理
import com.alibaba.fastjson.JSONObject;
import com.lzqs.yuanzilian.constant.ReturnMessage;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName NoPermissionException
* @Description
* @Author XinChunYu
* @Date 2020/5/30 15:12
* @Version 1.0
**/
@ControllerAdvice
public class NoPermissionException {
/**
* 用戶角色權(quán)限認證 當沒有權(quán)限時會報UnauthorizedException異常 此處處理異常給前端返回提示語
* @param response
* @param ex
* @throws IOException
*/
@ResponseBody
@ExceptionHandler(UnauthorizedException.class)
public void handleShiroException(HttpServletResponse response, Exception ex) throws IOException {
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
httpServletResponse.setContentType("application/json; charset=utf-8");
httpServletResponse.setCharacterEncoding("UTF-8");
//ReturnMessage returnMessage = ReturnMessage.failWithMsg(AuthConstant.AUTHENTICATE_FAIL);
httpServletResponse.getWriter().print(JSONObject.toJSONString(ReturnMessage.failWithMsg("訪問了無權(quán)限目錄")));
}
//執(zhí)行認證邏輯認證不通過時會走 過濾器AuthFilter中的onAccessDenied方法 所以這個異常不捕獲
/* @ResponseBody
@ExceptionHandler(AuthorizationException.class)
public String AuthorizationException(Exception ex) {
return "權(quán)限認證失敗";
}*/
}
jwt token工具類
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.util.Date;
/**
* @ClassName JWTUtil
* @Description
* @Author XinChunYu
* @Date 2020/5/29 13:18
* @Version 1.0
**/
public class JWTUtil {
// 過期時間50分鐘
private static final long EXPIRE_TIME = 50*60*1000;
/**
* 校驗token是否正確
* @param token 密鑰
* @param secret 用戶的密碼
* @return 是否正確
*/
public static boolean verify(String token, String username, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username)
.build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}
/**
* 獲得token中的信息無需secret解密也能獲得
* @return token中包含的用戶名
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 生成簽名,5min后過期
* @param username 用戶名
* @param secret 用戶的密碼
* @return 加密的token
*/
public static String sign(String username, String secret) {
try {
Date date = new Date(System.currentTimeMillis()+EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附帶username信息
return JWT.create()
.withClaim("username", username)
.withExpiresAt(date)
.sign(algorithm);
} catch (UnsupportedEncodingException e) {
return null;
}
}
public static String getRequestToken(HttpServletRequest request) {
return request.getHeader("token");
}
}
認證提示
package com.lzqs.yuanzilian.shiro.util;
/**
* @ClassName AuthConstant
* @Description 權(quán)限相關的常量
* @Author XinChunYu
* @Date 2020/5/29 14:06
* @Version 1.0
**/
public class AuthConstant {
/**
* cookie中存儲的token字段名
*/
public final static String COOKIE_TOKEN_NAME = "Authorization";
/**
* token有效時間 時*分*秒*1000L
*/
public final static Long EXPIRE_TIME = 3*60*1000L;//先設置3分鐘
//登錄認證結(jié)果,返回給前端
public final static String UNKNOWN_ACCOUNT = "登錄失敗, 用戶不存在。";
public final static String WRONG_PASSWORD = "登錄失敗糙臼,密碼錯誤。";
public final static String TOKEN_BLANK = "驗證失敗蒋伦,token為空弓摘,請登錄。";
public final static String TOKEN_INVALID = "驗證失敗痕届,token錯誤韧献。";
public final static String TOKEN_EXPIRE = "驗證失敗,token過期研叫,請重新登錄锤窑。";
public final static String AUTHENTICATE_FAIL = "無訪問權(quán)限,請嘗試登錄或聯(lián)系管理員嚷炉。";
}
ReturnMessage返回提示類
package com.lzqs.yuanzilian.constant;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 通用的返回類
**/
public class ReturnMessage implements Serializable {
//狀態(tài)碼 100-成功渊啰,200-失敗 400-帶信息失敗
private int code;
//提示信息
private String msg;
//用戶要返回給瀏覽器的數(shù)據(jù)
private Map<String, Object> data = new LinkedHashMap<>();
public static ReturnMessage successWithData(Map<String, Object> data) {
ReturnMessage result = new ReturnMessage();
result.setCode(100);
result.setMsg("成功");
result.setData(data);
return result;
}
public static ReturnMessage success() {
ReturnMessage result = new ReturnMessage();
result.setCode(100);
result.setMsg("成功");
return result;
}
public static ReturnMessage successWithMsg(String newmsg){
ReturnMessage result = new ReturnMessage();
result.setCode(100);
result.setMsg(newmsg);
return result;
}
public static ReturnMessage fail() {
ReturnMessage result = new ReturnMessage();
result.setCode(200);
result.setMsg("系統(tǒng)錯誤");
return result;
}
public static ReturnMessage failWithMsg(String newmsg) {
ReturnMessage result = new ReturnMessage();
result.setCode(400);
result.setMsg(newmsg);
return result;
}
/**
* 失敗 + 自定義msg,缺省為"處理失敗"
*
* @param newmsg
* @return
*/
public static ReturnMessage failWithMsg(int code, String newmsg) {
ReturnMessage result = new ReturnMessage();
result.setCode(code);
if (StringUtils.isEmpty(newmsg)) {
newmsg = "處理失敗";
}
result.setMsg(newmsg);
return result;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Map<String, Object> getData() {
return data;
}
public void setData(Map<String, Object> data) {
this.data = data;
}
public ReturnMessage add(String key, Object value) {
this.getData().put(key, value);
return this;
}
}
controller控制器
import com.lzqs.yuanzilian.constant.ReturnMessage;
import com.lzqs.yuanzilian.shiro.util.AuthConstant;
import com.lzqs.yuanzilian.shiro.util.JWTUtil;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Map;
/**
* @ClassName UserController
* @Description
* @Author XinChunYu
* @Date 2020/5/29 14:15
* @Version 1.0
**/
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
@ResponseBody
public ReturnMessage login(@RequestBody Map<String, Object> map){
String username = map.get("username").toString();
String password = map.get("password").toString();
User user = null;
for(User u : UserList.userList){
if(u.getUsername().equals(username)){
user = u;
}
}
if(user == null){
//賬號不存在
return ReturnMessage.failWithMsg(AuthConstant.UNKNOWN_ACCOUNT);
}else if(!user.getPassword().equals(password)){
//密碼錯誤
return ReturnMessage.failWithMsg(AuthConstant.WRONG_PASSWORD);
}else{
//通過認證, 生成簽名
String token = JWTUtil.sign(user.getUsername(), user.getPassword());
return ReturnMessage.success().add("token", token);
}
}
@RequiresPermissions("shop")
@RequestMapping("/shop")
@ResponseBody
public ReturnMessage shop(@RequestBody Map<String, Object> map){
return ReturnMessage.success();
}
@RequiresPermissions("order")
@RequestMapping("/order")
@ResponseBody
public ReturnMessage order(@RequestBody Map<String, Object> map){
return ReturnMessage.success();
}
/**
* 不加@RequiresPermissions 注解時不會進行用戶授權(quán)認證
* @param map
* @return
*/
@RequestMapping("/game")
@ResponseBody
public ReturnMessage game(@RequestBody Map<String, Object> map){
return ReturnMessage.success();
}
@RequestMapping("/address")
@ResponseBody
public ReturnMessage address(@RequestBody Map<String, Object> map){
return ReturnMessage.success();
}
}
用戶類
package com.lzqs.yuanzilian.shiro.controller;
import java.util.List;
/**
* @ClassName UserBean
* @Description 用戶表
* @Author XinChunYu
* @Date 2020/5/29 13:16
* @Version 1.0
**/
public class User {
private Long userId;
private String username;
private String password;
private String salt;//鹽值
private List<UserRole> userRoleList;
public User() {
}
public User(Long userId, String username, String password, String salt, List<UserRole> userRoleList) {
this.userId = userId;
this.username = username;
this.password = password;
this.salt = salt;
this.userRoleList = userRoleList;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
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;
}
public List<UserRole> getUserRoleList() {
return userRoleList;
}
public void setUserRoleList(List<UserRole> userRoleList) {
this.userRoleList = userRoleList;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
}
角色類
package com.lzqs.yuanzilian.shiro.controller;
import java.util.List;
/**
* @ClassName UserRole
* @Description 角色表
* @Author XinChunYu
* @Date 2020/5/29 13:34
* @Version 1.0
**/
public class UserRole {
private Long userRoleId;
private String userRoleName;
List<UserPermission> userPermissionList;
public UserRole(Long userRoleId, String userRoleName, List userPermissionList) {
this.userRoleId = userRoleId;
this.userRoleName = userRoleName;
this.userPermissionList = userPermissionList;
}
public Long getUserRoleId() {
return userRoleId;
}
public void setUserRoleId(Long userRoleId) {
this.userRoleId = userRoleId;
}
public String getUserRoleName() {
return userRoleName;
}
public void setUserRoleName(String userRoleName) {
this.userRoleName = userRoleName;
}
public List<UserPermission> getUserPermissionList() {
return userPermissionList;
}
public void setUserPermissionList(List<UserPermission> userPermissionList) {
this.userPermissionList = userPermissionList;
}
}
權(quán)限
package com.lzqs.yuanzilian.shiro.controller;
/**
* @ClassName Permission
* @Description 權(quán)限表
* @Author XinChunYu
* @Date 2020/5/29 13:35
* @Version 1.0
**/
public class UserPermission {
private Long userPermissionId;
private String userPermissionName;
public UserPermission(Long userPermissionId, String userPermissionName) {
this.userPermissionId = userPermissionId;
this.userPermissionName = userPermissionName;
}
public Long getUserPermissionId() {
return userPermissionId;
}
public void setUserPermissionId(Long userPermissionId) {
this.userPermissionId = userPermissionId;
}
public String getUserPermissionName() {
return userPermissionName;
}
public void setUserPermissionName(String userPermissionName) {
this.userPermissionName = userPermissionName;
}
}
用戶集合在這就不建表查數(shù)據(jù)庫了
package com.lzqs.yuanzilian.shiro.controller;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName UserList
* @Description
* @Author XinChunYu
* @Date 2020/5/29 16:15
* @Version 1.0
**/
public class UserList {
public static List<User> userList = new ArrayList<>();
static{
ArrayList<UserPermission> userPermissionArrayList1 = new ArrayList<>();
userPermissionArrayList1.add(new UserPermission(1l,"shop"));
userPermissionArrayList1.add(new UserPermission(2l,"game"));
userPermissionArrayList1.add(new UserPermission(1l,"order"));
userPermissionArrayList1.add(new UserPermission(1l,"address"));
ArrayList<UserRole> userRoleArrayList1 = new ArrayList<>();
userRoleArrayList1.add(new UserRole(1L, "老板", userPermissionArrayList1));
User user1 = new User(1l, "admin", "123456", "iwejfiwjf", userRoleArrayList1);
ArrayList<UserPermission> userPermissionArrayList2 = new ArrayList<>();
userPermissionArrayList2.add(new UserPermission(1l,"shop"));
userPermissionArrayList2.add(new UserPermission(2l,"game"));
ArrayList<UserRole> userRoleArrayList2 = new ArrayList<>();
userRoleArrayList2.add(new UserRole(2L, "經(jīng)理", userPermissionArrayList2));
User user2 = new User(2l, "xiaoming", "123456", "jfiosjfos", userRoleArrayList2);
userList.add(user1);
userList.add(user2);
}
}
maven坐標
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.0</version>
</dependency>