1. 定义一个使用 账号和密码登录 Realm
public class CustomRealm extends AuthorizingRealm {
@Autowired
RedisUtil redisUtil;
@Override
public String getName(){
return "myRealm";
}
@Autowired
private IUserService userService;
/**
* 进行权限校验的时候会调用
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("授权 doGetAuthorizationInfo");
String token = (String) principalCollection.getPrimaryPrincipal();
User currentUser = redisOperator.getAccUserInfo(token);
User user = userService.findAllUserInfoByUsername(currentUser.getId());
Set<String> stringRoleList = new HashSet<>();
List<String> stringPermissionList = new ArrayList<>();
List<AccRole> roleList = user.getRoleList();
for (AccRole role : roleList) {
stringRoleList.add(role.getRoleName());
List<UserPermission> permissionList = role.getPermissionsList();
for (UserPermission permission : permissionList) {
if (permission != null) {
stringPermissionList.add(permission.getPermissionName());
}
}
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRoles(stringRoleList);
simpleAuthorizationInfo.addStringPermissions(stringPermissionList);
return simpleAuthorizationInfo;
}
/**
* 用户认证登录的时候会调用
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("认证 doGetAuthenticationInfo");
// 从token获取用户信息,token代表用户输入
String username = (String) authenticationToken.getPrincipal();
AccUser currentUser = accUserService.getUserByUsername(username);
AccUser user = accUserService.findAllUserInfoByUsername(currentUser.getId());
// 取密码
String pwd = user.getPassword();
if (StringUtils.isEmpty(pwd)) {
return null;
}
return new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName());
}
}
2. 定义一个 token 结构体
public class CustomToken implements HostAuthenticationToken {
private String appid;
private String appKey;
private String token;
private String uid;
public CustomToken(String appid, String appKey, String token,String uid) {
this.appid = appid;
this.appKey = appKey;
this.token = token;
this.uid = uid;
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getAppKey() {
return appKey;
}
public void setAppKey(String appKey) {
this.appKey = appKey;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
@Override
public String getHost() {
return null;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
3. 定义一个自定义 Shiro 异常
public class CustomAuthenticationException extends AuthenticationException {
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public CustomAuthenticationException( Integer code,String message) {
super(message);
this.code = code;
}
public CustomAuthenticationException(ReturnEnum returnEnum) {
super(returnEnum.getRet_msg());
this.code = returnEnum.getRet_code();
}
}
4. 定义一个用于 token 验证的 Realm
public class TokenRealm extends AuthorizingRealm {
private static final Logger log = LoggerFactory.getLogger(AuthorizingRealm.class);
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof CustomToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.debug("进行token 认证 ............");
CustomToken customToken = (CustomToken) authenticationToken;
RedisUtil redisUtil = SpringContextBean.getBean(redisUtil.class);
IUserService userService = SpringContextBean.getBean(IUserService.class);
String uid = customToken.getUid();
String appid = customToken.getAppid();
String token = customToken.getToken();
if (StringUtils.isEmpty(appid)) {
//
if (!redisTempleUtil.hHasKey(GlobalConstant.USER_KEY, customToken.getToken())) {
throw new CustomAuthenticationException(ReturnEnum.UNKNOWN_TOKEN);
}
} else {
if (StringUtils.isEmpty(uid)) {
throw new CustomAuthenticationException(ReturnEnum.PARAM_INVALID.getRet_code(), "uId " + ReturnEnum.PARAM_INVALID.getRet_msg());
}
ReturnResult<JSONObject> authRes = userService.authToken(uid, appid, token);
log.warn("请求结果: {}",authRes);
if (authRes.getRet_code() != ReturnEnum.OK.getRet_code()) {
throw new CustomAuthenticationException(authRes.getRet_code(), authRes.getRet_msg());
}
}
return new SimpleAuthenticationInfo(token,token,"tokenRealm");
}
}
5. 创建一个用于 Filter 处理 token
public class TokenFilter extends AuthenticatingFilter {
private static final Logger log = LoggerFactory.getLogger(AuthenticatingFilter.class);
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (this.isLoginRequest(request,response)) { return true;}
boolean allowed = false;
try {
allowed = executeLogin(request,response);
} catch (IllegalAccessException e) {
log.error("未查找到token");
throw new RuntimeException(e.getMessage());
}catch (Exception e) {
log.error("登录过程发生异常",e);
throw new RuntimeException(e.getMessage());
}
return allowed || super.isPermissive(mappedValue);
}
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
CustomToken token = (CustomToken) this.createToken(request,response);
if (token == null) {
return false;
}else {
try {
Subject subject = this.getSubject(request, response);
subject.login(token);
return this.onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException var5) {
return this.onLoginFailure(token, var5, request, response);
}
}
}
/**
* 这里重写了父类的方法,使用我们自己定义的Token类,提交给shiro。
* 这个方法返回null的话会直接抛出异常,进入isAccessAllowed()的异常处理逻辑。
*/
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
String token = getHeader((HttpServletRequest)servletRequest, GlobalConstant.TOKEN_NAME);
String uid = getHeader((HttpServletRequest)servletRequest, GlobalConstant.UID);
String appid = getHeader((HttpServletRequest)servletRequest, GlobalConstant.APPID);
String appkey = getHeader((HttpServletRequest)servletRequest, GlobalConstant.APP_KEY);
if (!StringUtils.isEmpty(token)) {
// 如果 token 不为空
return new CustomToken(appid,appkey,token,uid);
}
return null;
}
private String getHeader(HttpServletRequest request,String headerName) {
return request.getHeader(headerName);
}
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
HttpServletResponse httpResponse = WebUtils.toHttp(servletResponse);
httpResponse.setCharacterEncoding("UTF-8");
httpResponse.setContentType("application/json;charset=UTF-8");
httpResponse.setStatus(HttpStatus.NON_AUTHORITATIVE_INFORMATION.value());
httpResponse.getWriter().write(JSON.toJSONString(ResponseVO.error(ReturnEnum.TOKEN_MISS.getRet_code(), ReturnEnum.TOKEN_MISS.getRet_msg())));
return false;
}
}
6. 角色过滤 filter
public class CustomRolesOrAuthorizationFilter extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception {
Subject subject = getSubject(servletRequest, servletResponse);
//获取当前访问路径所需要的角色集合
String[] rolesArray = (String[]) mappedValue;
//没有角色限制,有权限访问
if (rolesArray == null || rolesArray.length == 0) {
return true;
}
//当前subject是rolesArray中的任何一个,则有权限访问
for (int i = 0; i < rolesArray.length; i++) {
if (subject.hasRole(rolesArray[i])) {
return true;
}
}
return false;
}
}
7. 创建 shiro 的 config
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//必须设置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
//需要登录的接口,如果访问该接口却没有登录,则调用此接口
shiroFilterFactoryBean.setLoginUrl("/user/need_login");
//登录成功跳转URL(可以忽略)
shiroFilterFactoryBean.setSuccessUrl("/");
//已登录但没有权限访问要访问的接口,则调用此接口 (先验证登录->在验证权限)
shiroFilterFactoryBean.setUnauthorizedUrl("/user/not_permit");
//拦截器路径必须是LinkedHashMap,否则可能导致部分路径不起作用,因为hashmap是无序的
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//设置自定义filter
Map<String, Filter> filtersMap = new LinkedHashMap<>();
filtersMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
filtersMap.put("authcToken",new TokenFilter());
filterChainDefinitionMap.put("/admin/**","roleOrFilter[admin,root]");
shiroFilterFactoryBean.setFilters(filtersMap);
//匿名可以访问的(游客)
filterChainDefinitionMap.put("/doc.html/**","anon");
filterChainDefinitionMap.put("/user/login","anon");
filterChainDefinitionMap.put("/error","anon");
filterChainDefinitionMap.put("/swagger-resources/**","anon");
filterChainDefinitionMap.put("/webjars/**","anon");
filterChainDefinitionMap.put("/v2/**","anon");
filterChainDefinitionMap.put("/swagger-ui.html#/","anon");
//超级管理员可以访问的
// filterChainDefinitionMap.put("/root/**","roles[root]");
//过滤连是从上往下执行,将/**放到最后
//authc:url必须经过定义才能访问
//anon:url可以匿名访问
filterChainDefinitionMap.put("/**", "noSessionCreation,authcToken");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm推荐放到最后
securityManager.setRealms(Arrays.asList(customRealm(),tokenRealm()));
return securityManager;
}
/**
* 自定义realm
* @return
*/
@Bean
public CustomRealm customRealm(){
CustomRealm customRealm = new CustomRealm();
// 密码加解密规则
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置散列算法
credentialsMatcher.setHashAlgorithmName("md5");
//散列次数,相当于多次md5
credentialsMatcher.setHashIterations(2);
customRealm.setCredentialsMatcher(credentialsMatcher);
return customRealm;
}
@Bean
public TokenRealm tokenRealm(){
TokenRealm tokenRealm = new TokenRealm();
return tokenRealm;
}
/**
* 初始化Authenticator
*/
@Bean
public Authenticator authenticator( ) {
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
//设置两个Realm,一个用于用户登录验证和访问权限获取;一个用于jwt token的认证
authenticator.setRealms(Arrays.asList(customRealm(),tokenRealm()));
//设置多个realm认证策略,一个成功即跳过其它的
authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
return authenticator;
}
/**
* 管理shiro一些bean的生命周期 即bean初始化 与销毁
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 加入注解的使用,不加入这个AOP注解不生效(shiro的注解 例如 @RequiresGuest)
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
/**
* 用来扫描上下文寻找所有的Advistor(通知器), 将符合条件的Advisor应用到切入点的Bean中,
* 需要在LifecycleBeanPostProcessor创建后才可以创建
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
}