三个核心组件:Subject, SecurityManager 和 Realms.
Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息
先展示一下效果:
该角色添加了root角色
把角色改为admin
上代码:
1.导入jar包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
/**
*shiro异常捕获
* @param se
* @param model
* @return String
* @author zhangjunrong
* @date 2022/3/23 8:10
*/
@ExceptionHandler(ShiroException.class)
public String doHandleShiroException(ShiroException se, Model model) {
se.printStackTrace();
if(se instanceof UnknownAccountException) {
return "该账户不存在";
} else if (se instanceof LockedAccountException) {
return "该账户已锁定";
} else if (se instanceof IncorrectCredentialsException) {
return "密码错误请重试";
} else if (se instanceof AuthorizationException) {
return "没有相应权限";
} else {
return "操作失败请重试";
}
}
2.就是关于shiro的具体配置了
package com.zhang.config.shiro;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.zhang.utils.CorsUtil;
import org.apache.shiro.web.filter.authc.UserFilter;
import org.springframework.stereotype.Component;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
/**
* @Description 跨域问题
* @Author 小乌龟
* @Date 2022/3/17 16:37
*/
@Component
public class CorsAuthenticationFilter extends UserFilter {
private static final String OPTIONS_FOR_REQUEST = "OPTIONS";
public CorsAuthenticationFilter() {
super();
}
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (OPTIONS_FOR_REQUEST.equals(((HttpServletRequest) request).getMethod().toUpperCase())) {
return true;
}
return super.isAccessAllowed(request, response, mappedValue);
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletResponse res = (HttpServletResponse)response;
HttpServletRequest req = (HttpServletRequest)request;
CorsUtil.setResponseHeader(res, req);
res.setStatus(HttpServletResponse.SC_OK);
PrintWriter writer = res.getWriter();
Map<String, Object> map= new HashMap<>(16);
map.put("success", false);
map.put("code", 702);
map.put("message", "用户未登录");
map.put("time", DateUtil.now());
writer.write(JSON.toJSONString(map));
writer.close();
return false;
}
}
package com.zhang.config.shiro;
import com.zhang.utils.CorsUtil;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Description Shiro 的跨域过滤器
* @Author 小乌龟
* @Date 2022/3/17 16:27
*/
@Component
@WebFilter(urlPatterns = "/*",filterName = "shiroCrossFilter")
public class ShiroLoginFilter implements Filter {
private FilterConfig config = null;
@Override
public void init(FilterConfig config) throws ServletException {
this.config = config;
}
@Override
public void destroy() {
this.config = null;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest) servletRequest;
CorsUtil.setResponseHeader(response, request);
filterChain.doFilter( servletRequest, response );
}
}
package com.zhang.config.shiro;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
* @Description 创建自定义会话管理器
* @Author 小乌龟
* @Date 2022/3/17 16:34
*/
@Slf4j
@Component
public class CustomSessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public CustomSessionManager() {
super();
setGlobalSessionTimeout(DEFAULT_GLOBAL_SESSION_TIMEOUT * 48);
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if (!StringUtils.isEmpty(sessionId)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return sessionId;
} else {
return super.getSessionId(request, response);
}
}
}
package com.zhang.config.shiro;
import com.zhang.pojo.User;
import com.zhang.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @Description 安全实体数据源
* @Author 小乌龟
* @Date 2022/3/17 16:41
*/
@Component
@Slf4j
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private IUserService iUserService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 能进入这里说明用户已经通过验证了
User userInfo = (User) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// for (Role role : userInfo.getRoles()) {
simpleAuthorizationInfo.addRole("root");
// for (SysPermission permission : role.getPermissions()) {
// simpleAuthorizationInfo.addStringPermission(permission.getName());
// }
// }
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取用户输入的账户
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
System.err.println(token);
// 通过username从数据库中查找 UserInfo 对象
// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
User userInfo = iUserService.queryUserByName(Long.parseLong(token.getUsername()));
if (null == userInfo) {
return null;
}
System.err.println(getName()+"=="+userInfo);
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
userInfo, // 用户名
userInfo.getUserPassword(), // 密码
ByteSource.Util.bytes(userInfo.getUsername()+"yqs"), // salt=username+salt
getName() // realm name
);
return simpleAuthenticationInfo;
}
}
重点配置了
package com.zhang.config.shiro;
import cn.hutool.core.codec.Base64;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.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.web.servlet.handler.SimpleMappingExceptionResolver;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
/**
* @Description TODO
* @Author 小乌龟
* @Date 2022/3/17 16:49
*/
@Configuration
public class ShiroConfig {
@Autowired
private CorsAuthenticationFilter corsAuthenticationFilter;
@Autowired
private CustomSessionManager customSessionManager;
@Bean
public CookieRememberMeManager cookieRememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
simpleCookie.setMaxAge(259200000);
cookieRememberMeManager.setCookie(simpleCookie);
cookieRememberMeManager.setCipherKey(Base64.decode("6ZmI6I2j5Y+R5aSn5ZOlAA=="));
return cookieRememberMeManager;
}
@Bean
public ShiroFilterFactoryBean shirFilter(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("corsAuthenticationFilter", corsAuthenticationFilter);
shiroFilterFactoryBean.setFilters(filterMap);
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
// 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
// <!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/user/loginShow");
// 登录成功后要跳转的链接
// shiroFilterFactoryBean.setSuccessUrl("/ok.html");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5"); // 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2); // 散列的次数,比如散列两次,相当于 md5(md5(""));
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
@Bean
public ShiroRealm myShiroRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher hashedCredentialsMatcher) {
ShiroRealm myShiroRealm = new ShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return myShiroRealm;
}
@Bean
public DefaultWebSecurityManager securityManager(@Qualifier("myShiroRealm") ShiroRealm shiroRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(customSessionManager);
securityManager.setRealm(shiroRealm);
return securityManager;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager")DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean(name = "simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("DatabaseException", "databaseError"); // 数据库异常处理
mappings.setProperty("UnauthorizedException", "403");
r.setExceptionMappings(mappings); // None by default
r.setDefaultErrorView("error"); // No default
r.setExceptionAttribute("ex"); // Default is "exception"
//r.setWarnLogCategory("example.MvcLogger"); // No default
return r;
}
}
登入 退出
package com.zhang.controller;
import com.zhang.exception.YqsException;
import com.zhang.framework.CommonController;
import com.zhang.pojo.User;
import com.zhang.service.IUserService;
import com.zhang.utils.CorsUtil;
import com.zhang.utils.ResultResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.session.SessionException;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.ws.Response;
/**
* <p>
* 前端控制器
* </p>
*
* @author 小乌龟
* @since 2022-03-17
*/
@Controller
@Slf4j
@RequestMapping("/user")
public class UserController extends CommonController {
@Autowired
private IUserService iUserService;
@RequestMapping("/loginShow")
public String login(){
return "login";
}
@PostMapping(value = "/login")
public ResponseEntity<Void> login(@RequestBody User loginInfo, HttpServletRequest request, HttpServletResponse response){
Subject subject = SecurityUtils.getSubject();
try {
//将用户请求参数封装后,直接提交给Shiro处理
UsernamePasswordToken token = new UsernamePasswordToken(loginInfo.getUsername(), loginInfo.getUserPassword());
subject.login(token);
//Shiro认证通过后会将user信息放到subject内,生成token并返回
User user = (User) subject.getPrincipal();
// String newToken = iUserService.generateJwtToken(user.getUsername());
// response.setHeader("x-auth-token", newToken);
return ResponseEntity.ok().build();
} catch (AuthenticationException e) {
// 如果校验失败,shiro会抛出异常,返回客户端失败
log.error("User {} login fail, Reason:{}", loginInfo.getUsername(), e.getMessage());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping(value = "/logout")
public ResponseEntity<Void> logout() {
Subject subject = SecurityUtils.getSubject();
if(subject.getPrincipals() != null) {
User user = (User) subject.getPrincipals().getPrimaryPrincipal();
// iUserService.deleteLoginInfo(user.getUsername());
}
SecurityUtils.getSubject().logout();
return ResponseEntity.ok().build();
}
}