1.引入shiro
2.文件夹
ApplicationContextUtils:用于在shiro中需要集成service,但是由于service还没能加入配置中,比如redisService这样获取用户token的service,可以通过该类加入到spring配置文件applicationContext.xml中
ShiroCfg:对于shiro的一些配置
Token:自定义的token,用于shiro中的login方法
SecurityUtils.getSubject().login(new Token(token));
TokenFilter:自定义shiro的截取Filter,在shiroCfg中配置自定义filter,根据自定义的规则拦截
TokenMatcher:自定义matcher,用于shiro进行认证,里边没有多少内容
TokenRealm:自定义realm,用于认证,添加权限
ErrorFilter:用于拦截返回的错误,由于shiro中一些抛出的异常,不能使用我们自定义的异常通过Controller抛出,使用该类将shiro抛出的错误转发到定义的ErrorController再抛出
ErrorController:用于抛出解决shiro抛出的异常,不能按照自定义异常返回
3.代码片段
(1)ApplicationContextUtils
package cn.judouluo.shiro;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* shiro 自定义realm的认证阶段属于filter,当时的spring bean还没有读取进来
* 需要自己通过方法获取
*/
@Component
public class ApplicationContextUtilsimplements ApplicationContextAware {
private static ApplicationContextcontext =null;
public static T getBean(Class type) {
return context.getBean(type);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)throws BeansException {
if (ApplicationContextUtils.context ==null) {
ApplicationContextUtils.context = applicationContext;
}
}
}
(2)ShiroCfg
package cn.judouluo.shiro;
import cn.judouluo.filter.ErrorFilter;
import org.apache.shiro.realm.Realm;
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.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroCfg {
@Bean
public Realm realm(){return new TokenRealm(new TokenMatcher());}
@Bean
public DefaultWebSecurityManager securityManager(Realm realm){
return new DefaultWebSecurityManager(realm);
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(Realm realm,DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean filterBean =new ShiroFilterFactoryBean();
// 安全管理,添加relam
filterBean.setSecurityManager(securityManager);
// 添加自定义Filter
Map filters =new HashMap<>();
filters.put("token",new TokenFilter());
filterBean.setFilters(filters);
// 设置URL拦截
Map urlMap =new LinkedHashMap<>();
// 用户登录
urlMap.put("/user/login","anon");
// 将错误的打开
urlMap.put(ErrorFilter.ERROR_URI,"anon");
// 其他添加需要token
urlMap.put("/**","token");
filterBean.setFilterChainDefinitionMap(urlMap);
return filterBean;
}
/**
* 解决:@RequiresPermissions导致控制器接口404
*/
@Bean
public DefaultAdvisorAutoProxyCreator proxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator =new DefaultAdvisorAutoProxyCreator();
proxyCreator.setUsePrefix(true);
return proxyCreator;
}
/**
* 开启shiro aop注解支持.否则@RequiresRoles等注解无法生效
* 使用代理方式;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* Shiro生命周期处理器
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
/**
* 自动创建代理
* @return
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator =new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
}
(3)Token
package cn.judouluo.shiro;
import lombok.Data;
import org.apache.shiro.authc.AuthenticationToken;
/**
* 这个token不是请求时候带的token,只是在shiro中用于验证的时候需要的token
* 带有username,password
*/
@Data
public class Tokenimplements AuthenticationToken {
private final Stringtoken;
public Token(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
(4)TokenFilter
package cn.judouluo.shiro;
import cn.judouluo.pojo.vo.result.CodeMsg;
import cn.judouluo.redis.service.UserInfoRedisService;
import cn.judouluo.utils.JsonVos;
import org.apache.catalina.core.ApplicationContext;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
* 自定义的Filter
* 用于shiro中使用拦截的类型
* 表明哪些类型需要使用token进行拦截
* 作用:验证用户的合法性,是否有相关权限
*/
public class TokenFilterextends AccessControlFilter {
public static final StringHEADER_TOKEN ="Token";
@Autowired
UserInfoRedisServiceredisService;
/**
*当请求被TokenFilter拦截时,就会调用这个函数
* 可以在这个方法中初步判断
* 如果返回true:允许被访问,可以进入下一个链条调用)
* 比如Filter,拦截器,控制器
* 如果返回false:不允许访问,会进入onAccessDenied方法,不会进入下一个链条调用
* 一般都是返回false
*/
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o)throws Exception {
return false;
}
/**
* 当isAccessAllowed返回false时,就会调用这个方法
* 在这个方法中进行token的校验
*
* 如果返回true:允许访问。可以进入下一个链条调用(比如Filter、拦截器、控制器等)
* 如果返回false:不允许访问。不会进入下一个链条调用(比如Filter、拦截器、控制器等)
*/
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse)throws Exception {
HttpServletRequest request = (HttpServletRequest) servletRequest;
//从header中取出token
String token = request.getHeader(HEADER_TOKEN);
// 没有token
if (token ==null){
return JsonVos.raise(CodeMsg.NO_TOKEN);
}
// token过期
if (redisService ==null){
redisService = ApplicationContextUtils.getBean(UserInfoRedisService.class);
}
if (redisService.getUserByToken(token) ==null){
return JsonVos.raise(CodeMsg.TOKEN_EXPIRED);
}
// 鉴权,进入Realm
// 这里调用token,并不是登录的意思,只是为了触发Realm相应的方法去加载用户的角色,权限信息
SecurityUtils.getSubject().login(new Token(token));
System.out.println("走这里了吗??走走走啊11111"+"TokenFilter");
return true;
}
}
(5)TokenMatcher
package cn.judouluo.shiro;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
/**
* 判断调用login进去是假的判断,这里不需要进行任何操作返回true就可以
* 证明username和password通过
*/
public class TokenMatcherimplements CredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
return true;
}
}
(6)TokenRealm
package cn.judouluo.shiro;
import cn.judouluo.pojo.dto.SysUserDto;
import cn.judouluo.pojo.po.SysResource;
import cn.judouluo.pojo.po.SysRole;
import cn.judouluo.pojo.po.SysUser;
import cn.judouluo.redis.service.UserInfoRedisService;
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;
import org.apache.shiro.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.List;
public class TokenRealm extends AuthorizingRealm {
@Resource
UserInfoRedisService redisService;
public TokenRealm(TokenMatcher tokenMatcher){
super(tokenMatcher);
}
@Override
public boolean supports(AuthenticationToken token) {
return tokeninstanceof Token;
}
/**
* 获取权限和角色的
* @param principalCollection 用户名,这里都是Token
* @return
*/
@Override
protected AuthorizationInfodoGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取当前登录用户的token
String token = (String)principalCollection.getPrimaryPrincipal();
// 获取当前用户
SysUserDto user =redisService.getUserByToken(token);
SimpleAuthorizationInfo info =new SimpleAuthorizationInfo();
List roles = user.getRoles();
if (CollectionUtils.isEmpty(roles))return info;
// 添加角色
for (SysRole role : roles) {
info.addRole(role.getName());
}
List resources = user.getResources();
if (CollectionUtils.isEmpty(resources))return info;
// 添加权限
for (SysResource resource : resources) {
info.addStringPermission(resource.getPermission());
}
return info;
}
@Override
protected AuthenticationInfodo GetAuthenticationInfo(AuthenticationToken authenticationToken)throws AuthenticationException {
String tk = ((Token)authenticationToken).getToken();
//这里会调用TokenMatcher中的方法
System.out.println(getName());
return new SimpleAuthenticationInfo(tk, tk, getName());
}
}
(7)ErrorFilter
package cn.judouluo.filter;
import javax.servlet.*;
import java.io.IOException;
/**
* 过滤器
* 一定要放在最前边,使用try-catch将放在最前边,一旦发生错误将错误的派发给ErrorController
* 让ErrorController抛出异常,这样就可以使用公共的异常类
* 这是由于commonExpection 抛出异常需要的是控制器中抛出,如果不在控制器中返回是不可能截取的
* 所以需要在这个地方将将这个异常通过控制器抛出
*
*/
public class ErrorFilterimplements Filter {
public static final StringERROR_URI ="/handleError";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException {
try {
filterChain.doFilter(servletRequest, servletResponse);
}catch (Exception e) {
servletRequest.setAttribute(ERROR_URI, e);
// // 将错误转派到ERROR_URL,使其控制器抛出异常,
// 切记:一定要在ShiroCgf中打开这个错误的url
servletRequest.getRequestDispatcher(ERROR_URI).forward(servletRequest, servletResponse);
}
}
}
(8)ErrorController
package cn.judouluo.controller;
import cn.judouluo.filter.ErrorFilter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
public class ErrorController {
@RequestMapping(ErrorFilter.ERROR_URI)
public void handle(HttpServletRequest request)throws Exception {
// 抛出异常
throw (Exception) request.getAttribute(ErrorFilter.ERROR_URI);
}
}