前置
<!--shiro与spring整合 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<!--shiro与redis整合实现sessionDao -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.0.0</version>
</dependency>
redis:
host: 47.115.203.188
port: 6604
password: flowerpower
多对多查询什么的之类的就省略了吧
父工程
这个baseController是给子controller拿来继承
public class BaseController {
public HttpServletRequest request;
public HttpServletResponse response;
protected String companyId;
protected String companyName;
//shiro方式
@ModelAttribute
public void setResAndReq(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
//获取session中的安全数据
Subject subject = SecurityUtils.getSubject();
//获取安全数据集合
PrincipalCollection principals = subject.getPrincipals();
if(principals!=null && !principals.isEmpty()){
//获取安全数据
ProfileResult result = (ProfileResult) principals.getPrimaryPrincipal();
this.companyId = result.getCompanyId();
this.companyName = result.getCompany();
}
}
}
配置公共的realm,这个realm的作用是用于授权的。因为授权信息是从session里存着,每个微服务都是同一个获取用法
public class IhrmRealm extends AuthorizingRealm {
/**
* 搞这个主要是免得realm多时重名冲突了
*/
public void setName(String name) {
super.setName("ihrmRealm"); //customRealm
}
@Override//授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取安全数据,注意不是user,因为认证传的不是user
ProfileResult result = (ProfileResult) principals.getPrimaryPrincipal();
//获取权限信息
Set<String> apisPerms = (Set<String>) result.getRoles().get("apis");
//构造返回权限数据
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(apisPerms);
return info;
}
@Override//认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
/**
* 这里不需要写,应该交由子类来认证,因为每个微服务都需要不同的认证信息。
* 我们只要的是公共的权限信息,由上面授权部分来完成
*/
return null;
}
}
然后是配置自定义的sessionManager,用于拿出header里的token(sessionId)
public class CustomSessionManager extends DefaultWebSessionManager {
/**
* 指定sessionId 的获取方式
* 头信息中具有sessionid
* 请求头:Authoirzation: sessionId
*/
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//获取请求头的数据
String id = WebUtils.toHttp(request).getHeader("Authorization");
if (StringUtils.isEmpty(id)) {
//没有就生成一个
return super.getSessionId(request, response);
} else {
id = id.replaceAll("Bearer ", "");
//返回sessionId注意开头有Bearer
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");//哪里获取
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);//id是啥
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);//要不要验证
return id;
}
}
}
最好还是给一个公共的认证错误或未登录的controller
@RestController//公共错误跳转
public class ErrorController {
@RequestMapping("/autherror")
public Result autherror(int code){
return code==1?Result.FAIL(ResultCode.UNAUTHENTICATED):Result.FAIL(ResultCode.UNAUTHORISE);
}
}
子工程
子dependency里面,继承父的realm,重写认证方法
//子类不用重写realm名
public class UserRealm extends IhrmRealm {
@Resource
private UserService userService;
@Resource
private PermissionService permissionService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取到用户的手机号密码
UsernamePasswordToken uptoken = (UsernamePasswordToken) token;
String mobile = uptoken.getUsername();
String password = new String(uptoken.getPassword());
//根据手机号查询用户
User user = userService.resultWithAll(new QueryWrapper<User>().lambda().eq(User::getMobile, mobile));
//判断用户存在与否,密码一致与否
if (user != null && user.getPassword().equals(password)) {
//安全数据构造与返回(用户基本数据和权限信息就可,否则数据太多了.profileResult)
ProfileResult profileResult = null;
//根据不同的用户级别来获取用户权限
//如果是普通用户(user),则只有特定用户自己的权限
//如果是coAdmin管理员则给出全体权限
//平台全体管理员呢???
if (user.getLevel().equals("user")) {
profileResult = new ProfileResult(user);
} else {
Map<String, Object> map = new HashMap<>();
if ("coAdmin".equals(user.getLevel()))
// 1仅查询企业的权限,企业权限应当一视同仁。0是隐藏的。
map.put("enVisible", 1);
List<Permission> all = permissionService.findAll(map);
profileResult = new ProfileResult(user, all);
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(profileResult, user.getPassword(), this.getName());
return info;
}
//null会返回异常,表示用户名密码不匹配
return null;
}
}
最重点是这个自定义的shiro配置
@Configuration
public class ShiroConfiguration {
//创建Realm
@Bean
public IhrmRealm getRealm() { return new UserRealm(); }
//创建安全管理器
@Bean(name = "securityManager")
public SecurityManager getSecurityManager(IhrmRealm customRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm);
//讲自定义的会话管理器注册到安全管理器
securityManager.setSessionManager(redisResessionManager());
//将自定义的redis缓存管理器注册到安全管理器中
securityManager.setCacheManager(redisCacheManager());
return securityManager;
}
//配置过滤器工厂
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
//web中,shiro进行权限控制全部是通过一组过滤器集合进行控制
//创建过滤器工厂
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
factoryBean.setSecurityManager(securityManager);
//通用配置(跳转登录页面,未授权跳转的页面)
factoryBean.setLoginUrl("/autherror?code=1");
factoryBean.setUnauthorizedUrl("/autherror?code=2");
/**
* 设置过滤器集合,有顺序的map
* key=url拦截地址,value=过滤器类型
*/
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/sys/login","anon");//此地址允许匿名
filterMap.put("/autherror","anon");//此地址允许匿名
filterMap.put("/**", "authc");//必须认证
factoryBean.setFilterChainDefinitionMap(filterMap);
return factoryBean;
}
//开启注解支持
@Bean(name = "authorizationAttributeSourceAdvisor")
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean(name = "defaultAdvisorAutoProxyCreator")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);//cglib方式
return defaultAdvisorAutoProxyCreator;
}
//redis的控制器,操作控制器(jar里有,不用自己写的)
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost("47.115.203.188");
redisManager.setPort(6604);
redisManager.setPassword("flowerpower");
return redisManager;
}
//缓存管理器
public RedisCacheManager redisCacheManager() {
RedisCacheManager cacheManager = new RedisCacheManager();
cacheManager.setRedisManager(redisManager());
return cacheManager;
}
//sessionDao
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO sessionDAO = new RedisSessionDAO();
sessionDAO.setRedisManager(redisManager());
return sessionDAO;
}
//自定义的会话管理器了
public DefaultWebSessionManager redisResessionManager() {
CustomSessionManager sessionManager = new CustomSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
//禁用cookie,禁用url重写(太难看?)
sessionManager.setSessionIdCookieEnabled(false);
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
}
Controller里的登录、验证、权限等等。
@ApiOperation("Shiro用户登录")
@PostMapping("/login")
public Result login(@RequestBody Map<String, String> map) {
String mobile = map.get("mobile");
String password = map.get("password");
try {
//构造登录令牌
password = new Md5Hash(password, mobile, 1020).toString();
UsernamePasswordToken uptoken = new UsernamePasswordToken(mobile, password);
//获取subject
Subject subject = SecurityUtils.getSubject();
//调用subject.login方法,进入realm完成用户认证。
subject.login(uptoken);
//获取sessionId
String sessionId = (String) subject.getSession().getId();
//构造返回结果
return Result.SUCCESS(sessionId);
} catch (AuthenticationException e) {
return Result.FAIL(ResultCode.MOBILEORPASSWORDERROR);
}
}
验证
@ApiOperation("Shiro登录成功返回用户信息")
@PostMapping("/profile")
public Result profile() {
Subject subject = SecurityUtils.getSubject();
//subject获取session中的安全数据
PrincipalCollection principals = subject.getPrincipals();
ProfileResult result = (ProfileResult) principals.getPrimaryPrincipal();
return Result.SUCCESS(result);
}
权限验证
@ApiOperation("根据ID删除User")
@RequiresPermissions("API-USER-DELETE")
@DeleteMapping(value = "/user/{id}", name = "API-USER-DELETE")
public Result deleteById(@PathVariable String id) {
boolean b = userService.deleteById(id);
return b ? Result.SUCCESS() : Result.FAIL();
}