一、pom文件所需
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.2</version>
</dependency>
二、Shiro配置类
package com.*.config.shiro;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
//创建一个shiro过滤器工厂
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
shiroFilterFactoryBean.setLoginUrl("/notLogin");
// 设置无权限时跳转的 url;
shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
// 设置拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//开放登陆接口
filterChainDefinitionMap.put("/login", "anon");
//其余接口一律拦截
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
System.out.println("Shiro拦截器工厂类注入成功");
return shiroFilterFactoryBean;
}
/**
* 注入 securityManager
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(customRealm());
return securityManager;
}
/**
* 自定义身份认证 realm;
* 必须写这个类,并加上 @Bean 注解,目的是注入 CustomRealm,
* 否则会影响 CustomRealm类 中其他类的依赖注入
* @return
*/
@Bean
public CustomRealm customRealm() {
return new CustomRealm();
}
//为当前shiro配置aop切面 使shiro与springAOP结合
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
三、身份认证及权限认证
package com.*.config.shiro;
import com.*.sys.user.mapper.UserMapper;
import com.*.sys.user.pojo.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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 java.util.List;
public class CustomRealm extends AuthorizingRealm {
private UserMapper userMapper;
@Autowired
private void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
/**
* 获取授权信息
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//System.out.println("————权限认证————");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//如果身份认证的时候没有传入User对象,这里只能取到userName
//也就是SimpleAuthenticationInfo构造的时候第一个参数传递需要User对象
User user = (User)principalCollection.getPrimaryPrincipal();
//获取用户所拥有的所有权限地址
List<String> reghtList = userMapper.getUserPowerUrlList(user.getId());
//设置权限集合
info.addStringPermissions(reghtList);
return info;
}
/**
* 获取身份验证信息
* Shiro中,最终是通过 Realm 来获取应用程序中的用户、角色及权限信息的。
* @param authenticationToken 用户身份信息 token
* @return 返回封装了用户信息的 AuthenticationInfo 实例
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//System.out.println("————身份认证方法————");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 根据用户名获取用户信息
User user = userMapper.queryUserByUserName(token.getUsername());
if (null != user && user.getStatus() == 1){
throw new AccountException("此用户已停用!");
}else if (null == user) {
throw new AccountException("用户名不正确!");
}else if (!user.getPassword().equals(new String((char[]) token.getCredentials()))) {
throw new AccountException("密码不正确!");
}
return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
}
}
四、异常捕捉类
@RestControllerAdvice
public class ExceptionController {
//捕捉 CustomRealm 抛出的异常
@ExceptionHandler(AccountException.class)
public ExtObject handleShiroException(Exception ex) {
return ExtObject.markLoginError(ex.getMessage());
}
//捕捉@RequiresPermissions注解,无权限时抛出的异常
@ExceptionHandler(UnauthorizedException.class)
public ExtObject handleShiroException1(Exception ex) {
return ExtObject.notRole("没有此权限!");
}
}
五、LoginController
@RestController
public class LoginController {
@GetMapping("notLogin")
public ExtObject notLogin() {
return ExtObject.markLoginError("您尚未登陆!");
}
@GetMapping("notRole")
public ExtObject notRole() {
return ExtObject.notRole("没有此权限!");
}
@GetMapping("index")
public ExtObject index(){
return ExtObject.markSuccessButNoData("登录成功!");
}
@GetMapping("logout")
public ExtObject logout() {
Subject subject = SecurityUtils.getSubject();
//注销
subject.logout();
return ExtObject.markSuccessButNoData("成功注销!");
}
@PostMapping("login")
public ExtObject login(@RequestParam String username, @RequestParam String password){
// 从SecurityUtils里边创建一个 subject
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 执行认证登陆
subject.login(token);
//根据权限,指定返回数据
return ExtObject.markSuccessButNoData("登录成功!");
}
}
六、测试权限拦截
/**
* @RequiresPermissions注解中的url就是此方法的接口路径,
* 对应权限表中的url路径,角色没有此权限就会抛出异常,
* 在异常捕捉类中,进行捕捉异常并返回无权限提示
* 如角色用权限,正常访问此接口
* 如接口上不加此注解,则只要登录成功就可访问
* 测试权限
* @return
*/
@RequiresPermissions("/test1")
@GetMapping("test1")
public ExtObject test1(){
return ExtObject.markSuccess("admin用户有此接口权限",null);
}
@RequiresPermissions("/test2")
@GetMapping("test2")
public ExtObject test2(){
return ExtObject.markSuccess("admin用户无此接口权限",null);
}
@GetMapping("test3")
public ExtObject test3(){
return ExtObject.markSuccess("登录后可以任意访问此接口",null);
}
/**
* 测试结果:
*
* 首先登录admin用户,登录成功。
*
* 访问 /test1 接口,正常返回接口数据
*
* 访问 /test2 接口,返回无权限提示信息
*
* 访问 /test3 接口,正常返回接口数据
*
*/