第一次写简书,打算用来做做开发的笔记本吧,以下是Shiro的使用案例,Shiro在SpringBoot框架下的使用,前端使用的是Vue脚手架,在老项目上测试的权限管理,所以只贴上了部分代码。
首先Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
三个核心组件:Subject, SecurityManager和 Realms.
Subject:代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realms: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
下面是在pom.xml中添加的依赖
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.8.20</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
使用redis缓存做session和权限cache的保存
一般下,Shiro的授权方式为代码方式和注解方式,
代码方式
Subject subject = SecurityUtils.getSubject();
// 判断用户身份是否为管理员
if (subject.hasRole("admin")){
//有权限do sth.
}else{
//无权限do sth.
}
// 判断用户是否拥有"listen:high"权限
// 注意一般权限的表达式为三段式或两段式,以:号隔开 *:*:* 或者 *:* 这种格式
// 此处例子代码匹配所有三段式和二段式的权限
if(subject.isPermitted("listen:high")){
//有权限do sth.
}else{
//无权限do sth.
}
注解方式
@RequestMapping(value = "/getUser")
@RequiresRoles(value = {"admin"},logical = Logical.OR)
@RequiresPermissions("listen:high")
public Object getUser() {
//do sth..
}
两种方式各有优劣,代码方式可以在方法内执行一些复杂的逻辑业务,但是请求已经进入了程序方法中,注解方式的好处是能将用户请求拦截在方法外,请求不进入方法,且使用较为方便,但是处理复杂的逻辑业务不是很灵活,需要自行捕捉抛出的异常类来实现业务逻辑。可按个人爱好选择,该dome使用的注解方式来完成。个人推荐注解方式。
以下是注解解析
@RequiresUser:当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。
@RequiresRoles: 当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当天Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。
@RequiresPermissions:当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行。
@RequiresGuest:使用该注解标注的类,实例,方法在访问或调用时,当前Subject可以是“gust”身份,不需要经过认证或者在原先的session中存在记录。
@RequiresAuthentication:使用该注解标注的类,实例,方法在访问或调用时,当前Subject必须在当前session中已经过认证。
该项目主要运用@RequiresRoles和@RequiresPermissions两个注解。
示例
//用户拥有user身份则进入
@RequiresRoles("user")
//用户拥有必须同时属于user和admin身份则进入
@RequiresRoles({"user","admin"})
//用户拥有user或者admin身份之一;修改 logical 为 OR 即可
@RequiresRoles(value={"user","admin"},logical=Logical.OR)
@RequiresPermissions 用法类似不在演示
项目结构为:
1544611423(1)_副本.pngPermission 权限实体
Permission.java
/**
* @author by. 不笑猫丶
* @date 2018年12月12日
*/
public class Permission {
private String name;
private String permission;
private String code;
public Permission(){}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
Roles 权身份实体
Roles.java
/**
* @author by. 不笑猫丶
* @date 2018年12月12日
*/
public class Roles {
private String role;
private String code;
private String permission;
public Roles() {}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
}
ShiroConfig Shiro配置类
ShiroConfig.java
/**
* @author by. 不笑猫丶
* @date 2018年12月12日
* shiro配置类
*/
@Configuration
public class ShiroConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Bean
public ShiroFilterFactoryBean shirFilter(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//注意过滤器配置顺序不能颠倒
//配置退出过滤器,修改了默认logou过滤器,清除相应的缓存信息
filterChainDefinitionMap.put("/easeApi/authc/user/change/Exit", "logout");
// 配置需要拦截的链接
filterChainDefinitionMap.put("/easeApi/authc/**", "ShiroAuthFilter");
Map<String, Filter> filterMap = new LinkedHashMap<>();
// 自定义的登录过滤器
filterMap.put("ShiroAuthFilter", new ShiroAuthFilter());
shiroFilterFactoryBean.setFilters(filterMap);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(3);//散列的次数,比如散列两次,相当于 md5(md5(md5("")));
return hashedCredentialsMatcher;
}
/**
* 注入自定义的Realm类
**/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
securityManager.setRealm(myShiroRealm());
return securityManager;
}
/**
* 自定义sessionManager,使用redisSessionDAO生成并保存session
**/
@Bean(name = "sessionManager")
public SessionManager sessionManager() {
MySessionManager mySessionManager = new MySessionManager();
mySessionManager.setSessionDAO(redisSessionDAO());
return mySessionManager;
}
/**
* 配置shiro redisManager
* @return
*/
@ConfigurationProperties(prefix = "spring.redis")
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setDatabase(3);
return redisManager;
}
/**
* cacheManager 缓存 redis实现
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* 通过redis RedisSessionDAO shiro sessionDao层的实现
* 使用shiro-redis插件
*/
@Bean("redisSessionDAO")
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
MyShiroRealm类自定义的Realm,用于身份认证和权限获取。
MyShiroRealm.java
/**
* @author by. 不笑猫丶
* @date 2018年12月12日
* 自定义权限匹配
*/
public class MyShiroRealm extends AuthorizingRealm {
private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
@Autowired
private UserService userService;
@Autowired
private UserDao userDao;
//通过用户名查找用户拥有权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取SimpleAuthenticationInfo中传来的username
String username = (String) principals.getPrimaryPrincipal();
Map<String,Object> map = acquire.getHashMap("username", username);
//得到用户实体
UserPrivacy userPrivacy = (UserPrivacy) userService.GetMsgformTable(3, map);
//得到用户身份的代码
String role = userPrivacy.getRoleCode();
//通过身份代码查找对应的权限代码的集合
List<String> perCodeList = userDao.GetRoles(role);
//权限代码的集合查找对应的权限表达式 如 "listen:high"
List<String> permission = userDao.GetPermission(perCodeList);
//添加用户权限
authorizationInfo.addStringPermissions(permission);
//添加用户角色
authorizationInfo.addRole(role);
return authorizationInfo;
}
//身份认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
logger.info("成功进入ShiroRealm认证器");
//从token中获取用户名.
String username = (String) token.getPrincipal();
SimpleAuthenticationInfo authenticationInfo;
Map<String,Object> map = acquire.getHashMap("username", username);
// 获取用户信息
UserPrivacy userPrivacy = (UserPrivacy) userService.GetMsgformTable(3, map);
if(userPrivacy != null) {
// 用户存在,检查用户状态 code = 0 为保护状态
Result result = userService.findNumcheck(username);
int CODE = result.getStatus();
if(CODE == 0){
throw new LockedAccountException();
} else {
// 用户存在,且不为保护状态,对密码进行md5转码并与前端传来的password比对
String password = EncryUtils.getMD5(userPrivacy.getPassword().getBytes());
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
authenticationInfo = new SimpleAuthenticationInfo(username,password,getName());//getName() realm name
}
} else {
return null;
}
return authenticationInfo;
}
/**
* 通过用户名清除缓存
*/
public void clearCache(String username) {
System.out.println("调用cache清理操作");
PrincipalCollection principals = new SimplePrincipalCollection(
new UserPrivacy(username), getName());
clearCache(principals);
}
@Override
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
AuthException类异常统一处理,因为项目是前后端分离的项目,异常捕捉后不能重定向,只返回json数组。此处只捕捉了UnauthorizedException异常,其他异常可自行实现。
AuthException.java
/**
* @author by. 不笑猫丶
* @date 2018年12月12日
*/
@ControllerAdvice
public class AuthException {
private static final Logger logger = LoggerFactory.getLogger(AuthException.class);
@ExceptionHandler(value = UnauthorizedException.class)//处理访问方法时权限不足问题
public void AuthcErrorHandler(HttpServletResponse res, Exception e) throws IOException {
logger.info("抛出UnauthorizedException权限异常");
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setContentType("application/json; charset=utf-8");
res.setStatus(HttpServletResponse.SC_OK);
PrintWriter writer = res.getWriter();
Map<String, Object> map= new HashMap<>();
map.put("status", 3);
map.put("msg", "权限不足");
writer.write(JSON.toJSONString(map));
writer.close();
}
}
ShiroAuthFilter自定义的 Shiro 登录认证过滤器,继承FormAuthenticationFilter类拦截权限不足的请求,并返回JSON数据
ShiroAuthFilter.java
/**
* @author by. 不笑猫丶
* @date 2018年12月12日
*/
public class ShiroAuthFilter extends FormAuthenticationFilter {
private static final Logger logger = LoggerFactory.getLogger(ShiroAuthFilter.class);
public ShiroAuthFilter() {
super();
}
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
//Always return true if the request's method is OPTIONS
if (request instanceof HttpServletRequest) {
if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
return true;
}
}
return super.isAccessAllowed(request, response, mappedValue);
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
logger.info("SHIROFILTER authc拦截");
HttpServletResponse res = (HttpServletResponse)response;
res.setHeader("Access-Control-Allow-Origin", "true");
res.setContentType("application/json; charset=utf-8");
res.setStatus(HttpServletResponse.SC_OK);
PrintWriter writer = res.getWriter();
Map<String, Object> map= new HashMap<>();
map.put("status", 3);
map.put("msg", "未登录");
writer.write(JSON.toJSONString(map));
writer.close();
//return false 拦截, true 放行
return false;
}
}
ShiroAutoAuthFilter类,自定义的WebFilter过滤器,该项目中用于基于cookie的自动登录场景,在session过期或者关闭浏览器时,session失效或不存在时,在用户cookie尚存的情况下,再次请求被保护的资源时,重新获取认证,在Vue单页面的情况下无需刷新页面重新获取JSESSIONID,该类在JSESSIONID不存在时会自动生成cookie分配到前端页面中。
ShiroAutoAuthFilter.java
/**
* @author by. 不笑猫丶
* @date 2018年12月12日
* /easeApi/authc/ 为受保护资源的路径
*/
@WebFilter(filterName = "shiroAutoAuthFilter", urlPatterns = {"/easeApi/authc/*"})
public class ShiroAutoAuthFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(ShiroAutoAuthFilter.class);
@Override
public void destroy() {
}
@Autowired
private RedisUtil redisUtil;
@Autowired
private UserDao userDao;
public static final String SESSIONID = "JSESSIONID";
public static final int MAXAGE = 1800;
public static final String AUTHORIZATION = "Authorization";
@SuppressWarnings("deprecation")
@Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {
logger.info("shiroAutoAuthFilter被调用");
HttpServletRequest request = (HttpServletRequest) arg0;
HttpServletResponse response = (HttpServletResponse) arg1;
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
//判断用于自动登录的cookie是否存在
Cookie UIDcookie = com.music.Tools.CookieTool.getCookieByName(request, "UID");
//用户给request添加header信息 添加Authorization头,保证此次请求不被拦截,实现不刷新页面自动认证的关键
MyHttpServletRequestWrapper httpReq = new MyHttpServletRequestWrapper(request);
if (UIDcookie != null) {
//获取securityManager管理器
SecurityManager securityManager = (SecurityManager)SpringUtil.getBean("securityManager");
SecurityUtils.setSecurityManager(securityManager);
String enUser = URLDecoder.decode(UIDcookie.getValue());
//得到username和password
String[] userArray = com.music.Tools.PBEUtils.decrypt(enUser).split("_");
String username = userArray[0];
String password = userArray[1];
Map<String, Object> map = acquire.getHashMap("username,password",username+","+password);
//检查帐号密码是否有效
boolean empty = userDao.QueryforPrivacy(map);
if (!empty) {
//拦截返回提示
authcReq(response);
return;
}
//获取当前JSESSIONID,判断是否存在
Cookie SUID = CookieTool.getCookieByName(request, SESSIONID);
if(SUID == null) {
logger.info("JSESSIONID为空直接执行登录操作");
//JSESSIONID为空直接执行登录操作,并设置JSESSIONID至前端,实现不刷新自动认证
ShiroTool.authLogin(httpReq, response, username, password);
arg2.doFilter(httpReq, response);
return;
}
//判断 JSESSIONID 是否存在redis中
boolean bol = redisUtil.hasKey(3, "shiro:session:"+SUID.getValue());
//redis检测JSESSIONID结果为若为false则调用登录操作
logger.info("redis检测JSESSIONID结果为 :"+bol);
if(!bol){
//不存在执行登录操作
ShiroTool.authLogin(httpReq, response, username, password);
} else {
// 存在,判断是否获得认证
logger.info("JSESSIONID存在,验证是否已认证");
boolean auth = ShiroTool.isAuthenticated(SUID.getValue(), request, response);
if (!auth){
// 否,获取认证
logger.info("JSESSIONID未认证,执行登录操作");
ShiroTool.authLogin(httpReq, response, username, password);
}else {
// 是,打印消息
logger.info("JSESSIONID已认证");
}
}
}
arg2.doFilter(httpReq, response);
}
private void authcReq(HttpServletResponse response) throws IOException {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("application/json; charset=utf-8");
PrintWriter writer = response.getWriter();
Map<String, Object> map= new HashMap<>();
map.put("status", 4);
map.put("msg", "未找到用户信息");
writer.write(JSON.toJSONString(map));
writer.close();
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
ShiroTool类shiro工具类,包括判断是否认证的方法和获取缓存用户信息,和login+设置cookie的操作
ShiroTool.java
/**
* @author by. 不笑猫丶
* @date 2018年12月12日
* Shiro 工具类
*/
public class ShiroTool {
private static final Logger logger = LoggerFactory.getLogger(ShiroTool.class);
/**
* 验证是否登陆
*/
public static boolean isAuthenticated(String sessionID,HttpServletRequest request,HttpServletResponse response){
boolean status = false;
SessionKey key = new WebSessionKey(sessionID,request,response);
try{
Session se = SecurityUtils.getSecurityManager().getSession(key);
Object obj = se.getAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
if(obj != null){
status = (Boolean) obj;
}
}catch(Exception e){
e.printStackTrace();
}finally{
Session se = null;
Object obj = null;
}
return status;
}
/**
* 获取用户登录信息
*/
public static UserPrivacy getUserPrivacy(String sessionID, HttpServletRequest request, HttpServletResponse response){
boolean status = false;
SessionKey key = new WebSessionKey(sessionID,request,response);
try{
Session se = SecurityUtils.getSecurityManager().getSession(key);
Object obj = se.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
SimplePrincipalCollection coll = (SimplePrincipalCollection) obj;
return (UserPrivacy)coll.getPrimaryPrincipal();
}catch(Exception e){
e.printStackTrace();
}finally{
}
return null;
}
public static void authLogin(MyHttpServletRequestWrapper request, HttpServletResponse response, String username, String password){
//JSESSIONID为 :true 执行登录操作
logger.info("Shiro执行登录操作");
Subject subject = SecurityUtils.getSubject();
String sidVal = (String) subject.getSession().getId();
Cookie SIDCookie = CookieTool.setCookie(ShiroAutoAuthFilter.SESSIONID , sidVal, ShiroAutoAuthFilter.MAXAGE);
CookieTool.addCookie(response, SIDCookie, true);
request.putHeader(ShiroAutoAuthFilter.AUTHORIZATION, sidVal);
logger.info("subject生成的sessionID :"+sidVal);
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);
}
}
MySessionManager类自定义的session获取类
MySessionManager.java
/**
* @author by. 不笑猫丶
* @date 2018年12月12日
* 自定义session获取
*/
public class MySessionManager extends DefaultWebSessionManager {
private static final Logger logger = LoggerFactory.getLogger(MySessionManager.class);
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public MySessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
//如果请求头中有 Authorization 则其值为sessionId
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
logger.info("Session管理器检测到AUTHORIZATION token 使用此token作为Session");
return id;
} else {
//否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}
以上Shiro的配置和一些基本使用已经全部搭建好,可以开始测试了。
测试之前,说一点Dao层需注意的一点,若要实现动态的修改用户权限范围或者身份,需要更改身份时清除对应的认证缓存,否则身份修改后,缓存还在的情况下,shiro会读取缓存中之前的身份信息,所以可前端请求修改User身份时清除redis中的cache权限缓存信息,则下次用户访问受保护的资源时会再次调用权限获取方法 doGetAuthorizationInfo 从数据库中获取相应权限和身份,此时获取的数据为修改后的数据,可以实现简单的用户权限变更操作。
部分Dao层代码
/**
* 根据 T 实体修改与column字段相匹配的数据信息
* @param userPrivacy 实体类
* @param column 字段名
* @param value 字段对应值
* @return TRUE or FALSE
*/
@Override
public boolean UpdateToPrivacy(UserPrivacy userPrivacy, String column, String value){
Integer result = mapper.getUserPrivacyMapper().update(userPrivacy, new EntityWrapper<UserPrivacy>().eq(column, value));
if (result > 0){
if(userPrivacy.getRoleCode() != null){
RealmSecurityManager securityManager = (RealmSecurityManager) SecurityUtils.getSecurityManager();
//获取MyShiroRealm
MyShiroRealm userRealm = (MyShiroRealm)securityManager.getRealms().iterator().next();
//执行clearCache方法通过username清除缓存
userRealm.clearCache(value);
}
redisUtil.hset(0, "data_"+value,"update", 3, 604800);
return true;
}else {
return false;
}
}
Controller层
/**
* @author by. 不笑猫丶
* @date 2018年12月12日
*/
@RestController
@RequestMapping("/easeApi")
public class controllerTest {
/**
* 用户登录
* @param request
* @param username
* @return
*/
@RequestMapping("/auth/login")
public Object login() {
JSONObject jsonObject = new JSONObject();
Subject subject = SecurityUtils.getSubject();
subject.getSession().getId();
String username = "123456";
String password = "123456";
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
jsonObject.put("token", subject.getSession().getId());
jsonObject.put("msg", "登录成功");
} catch (IncorrectCredentialsException e) {
jsonObject.put("msg", "密码错误");
} catch (LockedAccountException e) {
jsonObject.put("msg", "登录失败,该用户已被冻结");
} catch (AuthenticationException e) {
jsonObject.put("msg", "该用户不存在");
} catch (Exception e) {
e.printStackTrace();
}
return jsonObject;
}
@RequestMapping(value = "/authc/getUser")
//此处为了方便用1,2,3来代表用户身份
@RequiresRoles(value = {"2"},logical = Logical.OR)
@RequiresPermissions("listen:high")
public Object getUser(){
return "Success";
}
@RequestMapping(value = "/authc/changeData")
@RequiresRoles(value = "1")
public Object changeData(){
UserPrivacy userPrivacy = new UserPrivacy();
userPrivacy.setRoleCode("2"); //修改用户身份
String username = "123456";
boolean bol = userDao.UpdateToPrivacy(userPrivacy, "username", username);
return bol;
}
}
使用postMan测试
首先测试请求url 127.0.0.1:8080/easeApi/authc/changeData,得到以下结果
{
"msg": "未登录",
"status": 3
}
因为在上面changeData()方法中设置了@RequiresRoles(value = "1"),而一开始用户并没有登录所以拒绝访问被保护的资源
然后我们再来测试登录操作,
请求url 127.0.0.1:8080/easeApi/auth/login,得到以下结果:
{
"msg": "登录成功",
"token": "b4c00d4f-8724-4ff3-88f6-ba216a06e269"
}
用户登录后测试getUser(),由于初始登录的用户只拥有 "1" 这个身份,用户以 "1" 的身份访问getUser()方法结果如下:
{
"msg": "权限不足",
"status": 3
}
结果显示权限不足,因为用户当前的拥有的身份为 "1" ,而请求此方法所需要的权限为 "2" 以上,所以请求被拦截。
此时在执行changeData()身份修改方法讲用户身份修改为"2"成功则返回true,得到结果如下
true
true既修改成功。此时用户拥有了 "2" ,这里再强调一下,在执行changeData()方法的时候Dao层中会执行这串代码,清除对应的username缓存信息。
if(userPrivacy.getRoleCode() != null){
RealmSecurityManager securityManager = (RealmSecurityManager) SecurityUtils.getSecurityManager();
MyShiroRealm userRealm = (MyShiroRealm)securityManager.getRealms().iterator().next();
userRealm.clearCache(username);
}
因为缓存清除了,shiro会再次执行doGetAuthorizationInfo权限获取方法从数据库中获取用户权限信息,此时的数据为更新后的数据,从而实现用户身份和权限范围变更。
再次调用getUser(),得到以下结果
Success
大功告成!
文章没有贴出前端Vue的代码,可以使用axios发送AJAX请求即可完成同样的操作,注意做好Vue的跨域操作
在index.js中添加一下代码即可
proxyTable: {
'/root': {
// 测试环境
target: 'http://127.0.0.1:8080/', // 接口域名
changeOrigin: true, //是否跨域
pathRewrite: {
'^/root': '' //需要rewrite重写的,
}
},
},
以上就是SpringBoot+Shiro+Vue前后端分离开发的全部项目实现。本人小白难免出错,请谅解。
我是不笑猫丶
一只爱编程的猫。
网友评论