一、导入依赖
<!--shiro依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.8.0</version>
</dependency>
<!--shiro-redis依赖-->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.4.2.1-RELEASE</version>
</dependency>
二、配置shiro
创建ShiroConfig类配置shiro信息
@Configuration
public class ShiroConfig {
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
// 自定义拦截器 不进行代码展示,可以使用shiro原拦截器,自行百度
Map<String, Filter> filters = new HashMap<>();
filters.put("authc", new AuthJwtFilter());
shiroFilter.setFilters(filters);
// 注意过滤器配置顺序 不能颠倒
// 置不会被拦截的链接 顺序判断
Map<String, String> filterMap = new LinkedHashMap<String, String>();
filterMap.put("/captcha", "anon");
filterMap.put("/login", "anon");
filterMap.put("/**", "authc");//全验证
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
// SecurityManager 是 Shiro 架构的核心,通过它来链接Realm和用户(文档中称之为Subject.)
@Bean(name = "securityManager")
public SecurityManager securityManager(AuthRealm authRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(authRealm);
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager(0, null, 0, 0, null));
// 关闭Shiro自带的session 使用了JWT
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
return securityManager;
}
public RedisManager redisManager(int currentDatabase, String currentHost, int currentPort, int currentTimeout, String currentPassword) {
CustomRedisManager redisManager = new CustomRedisManager();
redisManager.setDatabase(currentDatabase);
redisManager.setHost(currentHost);
redisManager.setPort(currentPort);
redisManager.setTimeout(currentTimeout);
redisManager.setPassword(currentPassword);
return redisManager;
}
/**
* cacheManager 缓存 redis实现
* 使用的是shiro-redis开源插件
*
* @return
*/
@Bean
public RedisCacheManager cacheManager(@Value("${spring.redis.database}") int currentDatabase,
@Value("${spring.redis.host}") String currentHost,
@Value("${spring.redis.port}") int currentPort,
@Value("${spring.redis.timeout}") int currentTimeout,
@Value("${spring.redis.password}") String currentPassword) {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager(currentDatabase, currentHost, currentPort, currentTimeout, currentPassword));
return redisCacheManager;
}
/**
* 创建安全管理器
*/
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(AuthRealm authRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(authRealm);
return securityManager;
}
/**
* 开启Shiro注解
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(
DefaultWebSecurityManager defaultWebSecurityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public AuthRealm authRealm() {
return new AuthRealm();
}
@Bean
public EventBus eventBus() {
return new DefaultEventBus();
}
}
三、realm配置
@Slf4j
@Component
public class AuthRealm extends AuthorizingRealm {
@Autowired
private RedisCache redisCache;
// 必须重写,不然会报错
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof AuthenticationToken;
}
/**
* 授权,首次登录后将缓存数据存入redis,每次请求登录不会在重新授权,只有当redis授权数据失效后才会重新加载授权方法
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
log.info("开始执行授权操作!!!!!");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
CacheUserDto userDto = (CacheUserDto) principals.getPrimaryPrincipal();
Integer roleId = userDto.getRoleId();
// 赋予角色
Role roleApiOne = roleApi.getOne(roleId);
if (ObjectUtil.isNotEmpty(roleApiOne)) {
authorizationInfo.addRole(roleApiOne.getRoleName());
}
// 赋予权限
//TODO 详细代码省略
//查询角色和权限关联信息集合
SelRolePermissionDto rolePermission = new SelRolePermissionDto();
rolePermission.setRoleId(roleId);
List<RolePermission> rolePermissions = rolePermissionApi.getList(rolePermission);
if (ObjectUtil.isEmpty(rolePermissions)) {
return authorizationInfo;
}
//查询权限菜单集合
List<Menu> menus = menuApi.busGetList(menu);
// 将查询到的menuCode添加到用户权限信息中
if (ObjectUtil.isNotEmpty(menus)) {
Set<String> permissionSet = new HashSet<>();
for (Menu m : menus) {
permissionSet.add(m.getMenuCode());
}
/*
常见的方法:
addRole(String role):向授权信息中添加角色。
addRoles(Collection<String> roles):向授权信息中添加多个角色。
addStringPermission(String permission):向授权信息中添加权限(字符串形式)。
addStringPermissions(Collection<String> permissions):向授权信息中添加多个权限(字符串形式)。
setRoles(Set<String> roles):设置授权信息的角色集合。
setStringPermissions(Set<String> permissions):设置授权信息的权限集合。
getRoles():获取授权信息中的角色集合。
getStringPermissions():获取授权信息中的权限集合。
*/
authorizationInfo.setStringPermissions(permissionSet);
}
return authorizationInfo;
}
/**
* 认证信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String userIp = IPAddressUtils.getIpAdrress(request);
System.out.println("用户请求ip地址:" + userIp);
String token = (String) authenticationToken.getCredentials();
// 校验token是否过期
Claims claim = JwtToken.getClaimByToken(token);
if (claim == null || JwtToken.isTokenExpired(claim.getExpiration())) {
// token过期处理机制,目前过期后重新登录
throw new ExpiredCredentialsException("凭证已过期,请重新登录");
}
String jwtSubject = claim.getSubject();
TokenPayload tokenPayload = JSONUtil.toBean(JSONUtil.toJsonStr(jwtSubject), TokenPayload.class);
int userId = tokenPayload.getUserId();
String uuid = tokenPayload.getUuid();
String key = RedisUtil.getWebUserKey(userId, uuid);
CacheUserDto user = BeanUtil.toBean(redisCache.get(key), CacheUserDto.class);
if (user == null) {
clearCacheUser(key, token);
throw new ExpiredCredentialsException("登录凭证已失效,请重新登录!");
}
if (!user.getToken().equals(token)) {
clearCacheUser(key, token);
throw new ExpiredCredentialsException("登录凭证不一致,请重新登录!");
}
return new SimpleAuthenticationInfo(user, token, this.getName());
}
/**
* 清除Redis缓存中用户信息
*
* @param key
* @param item
*/
private void clearCacheUser(String key, String item) {
redisCache.hdel(key, item);
}
/**
* 重写 获取缓存名
*/
@Override
public String getAuthorizationCacheName() {
return "";
}
}
四、自定义RedisManager
/**
* 自定义org.crazycake.shiro.RedisManager
* 该自定义的Manager扩展的功能:
* 1.增加database参数,可跟随配置文件的数据库索引选择redis数据库保存相关数据,避免了多个项目共用一个redis database所潜在的问题
* 2.修改expire参数,默认值为30天 = 2592000s
*/
public class CustomRedisManager extends RedisManager {
private static JedisPool jedisPool = null;
private String host = "127.0.0.1";
private int port = 6379;
private int expire = 2592000;
private int timeout = 2000;
private int database = 0;
private String password = null;
public CustomRedisManager() {
}
@Override
public void init() {
this.password = StringUtils.isEmpty(this.password) ? null : this.password;
if (jedisPool == null) {
jedisPool = new JedisPool(new JedisPoolConfig(), this.host, this.port, this.timeout, this.password, database);
}
}
@Override
public byte[] get(byte[] key) {
byte[] value = null;
try (Jedis jedis = jedisPool.getResource()) {
value = jedis.get(key);
}
return value;
}
@Override
public byte[] set(byte[] key, byte[] value) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.set(key, value);
if (this.expire != 0) {
jedis.expire(key, this.expire);
}
}
return value;
}
@Override
public byte[] set(byte[] key, byte[] value, int expire) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.set(key, value);
if (expire != 0) {
jedis.expire(key, expire);
}
}
return value;
}
@Override
public void del(byte[] key) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.del(key);
}
}
@Override
public void flushDB() {
try (Jedis jedis = jedisPool.getResource()) {
jedis.flushDB();
}
}
@Override
public Long dbSize() {
Long dbSize = 0L;
try (Jedis jedis = jedisPool.getResource()) {
dbSize = jedis.dbSize();
}
return dbSize;
}
@Override
public Set<byte[]> keys(String pattern) {
Set<byte[]> keys = null;
try (Jedis jedis = jedisPool.getResource()) {
keys = jedis.keys(pattern.getBytes());
}
return keys;
}
@Override
public String getHost() {
return this.host;
}
@Override
public void setHost(String host) {
this.host = host;
}
@Override
public int getPort() {
return this.port;
}
@Override
public void setPort(int port) {
this.port = port;
}
@Override
public int getExpire() {
return this.expire;
}
@Override
public void setExpire(int expire) {
this.expire = expire;
}
@Override
public int getTimeout() {
return this.timeout;
}
@Override
public void setTimeout(int timeout) {
this.timeout = timeout;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public void setPassword(String password) {
this.password = password;
}
public int getDatabase() {
return database;
}
public void setDatabase(Integer database) {
if (null == database) {
return;
}
this.database = database;
}
}
五、使用注解
@RequiresAuthentication 表示subject已经通过登录验证,才可使用
@RequiresUser 表示subject已经身份验证或者通过记住我登录,才可使用
@RequiresGuest 表示subject没有身份验证或通过记住我登录过,即是游客身份,才可使用
@RequiresRoles(value={“admin”, “user”}, logical=Logical.AND) 表示subject需要xx(value)角色,才可使用
@RequiresPermissions (value={“user:a”, “user:b”},logical= Logical.OR) 表示subject需要xxx(value)权限,才可使用
@RequiresRoles(value = {
"admin", "管理员"}, logical = Logical.OR)
@RequiresPermissions(value = {
"sys:man:role"}, logical = Logical.AND)
@RequestMapping(value = "/getList", method = RequestMethod.POST)
public Result getList(@RequestBody SelRoleDto role) throws Exception {
}
六、捕捉异常
根据自身需求处理异常,我这里使用了全局捕获
@ExceptionHandler(UnauthorizedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public Result handleUnauthorizedException(HttpServletRequest request, UnauthorizedException e) {
// 在这里定义返回的信息或者跳转到指定的页面
log.error("无权限访问!!! {}", e.getMessage());
return Result.error("无权限访问");
}