org.apache.shiro
shiro-core
1.3.2
org.apache.shiro
shiro-spring
1.3.2
import com.cqjtu.platform.dao.ResourceDao;
import com.cqjtu.platform.entity.Role;
import com.cqjtu.platform.entity.User;
import com.cqjtu.platform.enums.ExceptionEnums;
import com.cqjtu.platform.exception.MyException;
import com.cqjtu.platform.service.RoleService;
import com.cqjtu.platform.service.UserService;
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 org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.*;
/**
* @author pengyangyan
*/
public class AuthRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@javax.annotation.Resource
private ResourceDao resourceDao;
@Autowired
private RoleService roleService;
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
User user = (User) principalCollection.fromRealm(this.getClass().getName()).iterator().next();
Set permissionSet = new HashSet<>();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Role role = roleService.selectByKey(user.getRoleId());
List roleResource = resourceDao.findRoleResource(user.getRoleId(),null);
if (!CollectionUtils.isEmpty(roleResource)){
roleResource.forEach(v->{
if (!StringUtils.isEmpty(v.getPermission())){
permissionSet.add(v.getPermission());
}
});
}
info.addRole(role.getRoleName());
info.setStringPermissions(permissionSet);
return info;
}
/**
* 认证登录
* @param authenticationToken
* @return
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken){
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String account = usernamePasswordToken.getUsername();
User user = userService.findByAccount(account);
if (Objects.isNull(user)){
throw new MyException(ExceptionEnums.ACCOUNT_IS_NOT_EXIT);
}
return new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName());
}
}
import com.cqjtu.platform.utils.MD5Utils;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
/**
* @author pengyangyan
*/
public class CredentialMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
String password = new String(usernamePasswordToken.getPassword());
String dbPassword = (String) info.getCredentials();
return this.equals(MD5Utils.encrypt(usernamePasswordToken.getUsername(),password),dbPassword);
}
}
MD5Utils为密码md5加密工具
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
/**
* @author pengyangyan
*/
public class MD5Utils {
protected MD5Utils(){
}
private static final String SALT = "platform";
private static final String ALGORITH_NAME = "md5";
private static final int HASH_ITERATIONS = 2;
public static String encrypt(String pswd) {
return new SimpleHash(ALGORITH_NAME, pswd, ByteSource.Util.bytes(SALT), HASH_ITERATIONS).toHex();
}
public static String encrypt(String username, String pswd) {
return new SimpleHash(ALGORITH_NAME, pswd, ByteSource.Util.bytes(username.toLowerCase() + SALT),
HASH_ITERATIONS).toHex();
}
}
package com.cqjtu.platform.shiro;
import com.cqjtu.platform.entity.Resource;
import com.cqjtu.platform.entity.User;
import com.cqjtu.platform.service.ResourceService;
import com.cqjtu.platform.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.RealmSecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import tk.mybatis.mapper.entity.Example;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author pengyangyan
*/
@Slf4j
@Service
public class ShiroService {
@Autowired
private ResourceService resourceService;
@Autowired
private UserService userService;
/**
* 初始化权限
*/
public Map loadFilterChainDefinitions() {
/*
配置访问权限
- anon:所有url都都可以匿名访问
- authc: 需要认证才能进行访问(此处指所有非匿名的路径都需要登陆才能访问)
- user:配置记住我或认证通过可以访问
*/
LinkedHashMap filterChainDefinitionMap = new LinkedHashMap();
// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/api/user/loginOut", "anon");
filterChainDefinitionMap.put("/error", "anon");
filterChainDefinitionMap.put("/loginUser", "anon");
//设置文件上传为匿名访问
filterChainDefinitionMap.put("/api/uploadFile/upload", "anon");
filterChainDefinitionMap.put("/login", "anon");
//未授权跳转url
filterChainDefinitionMap.put("/unauthorized","anon");
// 加载数据库中配置的资源权限列表
List resourcesList = resourceService.listUrlAndPermission();
for (Resource resource : resourcesList) {
if (!StringUtils.isEmpty(resource.getUrl()) && !StringUtils.isEmpty(resource.getPermission())) {
String permission = "perms[" + resource.getPermission() + "]";
filterChainDefinitionMap.put(resource.getUrl(), permission);
}
}
// 不存在什么特别关键的操作,所以直接使用user认证,支付相关用authc
filterChainDefinitionMap.put("/**", "user");
return filterChainDefinitionMap;
}
/**
* 重新加载权限
*/
public void updatePermission() {
ShiroFilterFactoryBean shirFilter = SpringContextHolder.getBean(ShiroFilterFactoryBean.class);
synchronized (shirFilter) {
AbstractShiroFilter shiroFilter = null;
try {
shiroFilter = (AbstractShiroFilter) shirFilter.getObject();
} catch (Exception e) {
throw new RuntimeException("get ShiroFilter from shiroFilterFactoryBean error!");
}
PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver();
DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();
// 清空老的权限控制
manager.getFilterChains().clear();
shirFilter.getFilterChainDefinitionMap().clear();
shirFilter.setFilterChainDefinitionMap(loadFilterChainDefinitions());
// 重新构建生成
Map chains = shirFilter.getFilterChainDefinitionMap();
for (Map.Entry entry : chains.entrySet()) {
String url = entry.getKey();
String chainDefinition = entry.getValue().trim().replace(" ", "");
manager.createChain(url, chainDefinition);
}
}
log.info("用户权限重新加载成功!!");
}
/**
* 重新加载用户权限
*
* @param user
*/
public void reloadAuthorizingByUserId(User user) {
RealmSecurityManager rsm = (RealmSecurityManager) SecurityUtils.getSecurityManager();
AuthRealm shiroRealm = (AuthRealm) rsm.getRealms().iterator().next();
Subject subject = SecurityUtils.getSubject();
String realmName = subject.getPrincipals().getRealmNames().iterator().next();
SimplePrincipalCollection principals = new SimplePrincipalCollection(user.getId(), realmName);
subject.runAs(principals);
shiroRealm.getAuthorizationCache().remove(subject.getPrincipals());
subject.releaseRunAs();
log.info("用户[{}]的权限更新成功!!", user.getAccount());
}
/**
* 重新加载所有拥有roleId角色的用户的权限
* @param roleId
*/
public void reloadAuthorizingByRoleId(Integer roleId) {
Example example = new Example(User.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("roleId",roleId);
List userList = userService.selectByExample(example);
if (CollectionUtils.isEmpty(userList)) {
return;
}
for (User user : userList) {
reloadAuthorizingByUserId(user);
}
}
}
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
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.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.Map;
/**
* @author pengyangyan
*/
@Configuration
public class ShiroConfiguration {
@Autowired
private ShiroService shiroService;
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
//登录界面url
bean.setLoginUrl("/api/user/login");
//登陆后跳转成功url
bean.setSuccessUrl("/success");
//未授权跳转url
bean.setUnauthorizedUrl("/unauthorized");
// 配置数据库中的resource
Map filterChainDefinitionMap = shiroService.loadFilterChainDefinitions();
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
@Bean("securityManager")
public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm,@Qualifier("cookieRememberMeManager") CookieRememberMeManager cookieRememberMeManager){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(authRealm);
manager.setRememberMeManager(cookieRememberMeManager);
return manager;
}
@Bean("cookieRememberMeManager")
public CookieRememberMeManager cookieRememberMeManager(@Qualifier("simpleCookie") SimpleCookie simpleCookie){
CookieRememberMeManager manager = new CookieRememberMeManager();
manager.setCipherKey(Base64.decode("cGxhdGZvcm0AAAAAAAAAAA=="));
manager.setCookie(simpleCookie);
return manager;
}
@Bean("simpleCookie")
public SimpleCookie simpleCookie(){
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
simpleCookie.setHttpOnly(true);
simpleCookie.setMaxAge(7*24*60*60);
return simpleCookie;
}
@Bean("authRealm")
public AuthRealm authRealm(@Qualifier("credentialMatcher") CredentialMatcher credentialMatcher){
AuthRealm authRealm = new AuthRealm();
authRealm.setCacheManager(new MemoryConstrainedCacheManager());
authRealm.setCredentialsMatcher(credentialMatcher);
return authRealm;
}
/**
* 凭证验证器
* @return
*/
@Bean("credentialMatcher")
public CredentialMatcher credentialMatcher(){
return new CredentialMatcher();
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
1、如果项目为前后端分离,则会涉及到相关的跨域问题,如Cros跨域所以接下来会涉及项目跨域允许配置的相关过滤器
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author pengyangyan
*/
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("utf-8");
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
//是否支持cookie跨域
httpServletResponse.setHeader("Access-Control-Allow-Credentials","true");
//指定允许其他域名访问
httpServletResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
//响应头设置
httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,client_id, uuid, Authorization,user-agent,x-csrftoken");
// 设置过期时间
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
//响应类型
httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
// 支持HTTP1.1.
httpServletResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
// 支持HTTP 1.0.
httpServletResponse.setHeader("Pragma", "no-cache");
httpServletResponse.setHeader("Allow","GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH");
if ("OPTIONS".equals(httpServletRequest.getMethod())) {
httpServletResponse.setStatus(204);
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* @author pengyangyan
*/
@Slf4j
public class ShiroFiter extends FormAuthenticationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
boolean allowed = super.isAccessAllowed(request, response, mappedValue);
if (!allowed) {
// 判断请求是否是options请求
String method = WebUtils.toHttp(request).getMethod();
if (StringUtils.equalsIgnoreCase("OPTIONS", method)) {
return true;
}
}
return allowed;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
return super.onAccessDenied(request, response);
}
}
import com.cqjtu.platform.filter.CorsFilter;
import com.cqjtu.platform.filter.ShiroFiter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author pengyangyan
*/
@Slf4j
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean crosFilterRegistration(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
//注入过滤器
registrationBean.setFilter(new CorsFilter());
//过滤器名称
registrationBean.setName("CorsFilter");
//拦截规则
registrationBean.addUrlPatterns("/*");
//过滤器顺序
registrationBean.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
log.info("开启cros-Filter过滤器。");
return registrationBean;
}
@Bean
public FilterRegistrationBean shiroFilterRegistration(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
//注入过滤器
registrationBean.setFilter(new ShiroFiter());
//过滤器名称
registrationBean.setName("ShiroFilter");
//拦截规则
registrationBean.addUrlPatterns("/*");
//过滤器顺序
registrationBean.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
log.info("开启shiro-Filter过滤器。");
return registrationBean;
}
}
2、Ajax的post请求出现options请求302,但是get请求正常。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* 用于解决springboot ajax post跨域请求 options 302
* @author pengyangyan
*/
@Configuration
public class CorsConfig {
private CorsConfiguration buildConfig(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",buildConfig());
return new CorsFilter(source);
}
}