Shiro简介
Apache Shiro 是一个强大且易用的 Java 安全框架,主要用于身份认证、授权、加密和会话管理。它的设计目标是简化安全性的实现,使开发者能够更专注于业务逻辑。以下是 Shiro 的主要作用和功能:
1. 身份认证(Authentication)
用户登录:Shiro 提供了简单而强大的 API 来处理用户登录。你可以通过 Subject 对象调用 login 方法来验证用户的身份。
多方式认证:Shiro 支持多种形式的认证,包括用户名/密码、证书、OpenID 等。
自定义认证:你可以通过实现自定义的 Realm 来扩展 Shiro 的认证功能,以适应不同的认证需求。
2. 授权(Authorization)
权限管理:Shiro 提供了细粒度的权限管理功能,可以基于角色、权限字符串等方式进行授权。
访问控制:Shiro 支持 URL 访问控制、方法级别的访问控制等,可以灵活地控制用户对资源的访问。
注解支持:Shiro 提供了注解支持,如 @RequiresRoles、@RequiresPermissions 等,可以在代码中方便地进行权限检查。
3. 会话管理(Session Management)
无状态会话:Shiro 支持无状态会话,适用于分布式系统和无状态应用,如使用 JWT 进行身份验证。
会话集群:Shiro 可以将会话信息存储在集群中,确保会话的一致性和可用性。
会话监听:Shiro 提供了会话监听器,可以监控会话的创建、更新和销毁事件。
4. 加密(Cryptography)
密码加密:Shiro 提供了多种密码加密算法,如 SHA-256、MD5 等,确保用户密码的安全。
对称加密:Shiro 支持对称加密算法,如 AES,用于保护敏感数据。
哈希和盐:Shiro 提供了哈希和盐的功能,可以增强密码的安全性。
5. Web 支持
过滤器:Shiro 提供了 ShiroFilter,可以轻松地集成到 Web 应用中,进行 URL 访问控制。
标签库:Shiro 提供了 JSP 标签库,可以在页面中进行权限检查和显示不同的内容
1、引入shiro的maven依赖包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.5</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>commons-beanutils</artifactId>
<groupId>commons-beanutils</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.5</version>
</dependency>
2、使用token自动登录
1)使用身份验证过滤器BasicHttpAuthenticationFilter,验证用户是否为登录状态,接口调用时生效;
如果为否执行登录,登录失败会返回401错误。
public class JWTFilter extends BasicHttpAuthenticationFilter {
/**
* 检查用户是否登录
* @param request
* @param response
* @return
*/
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
// 验证请求头中是否存在Authorization字段,不存在则返回false
String header = req.getHeader(SecurityUtils.REQUEST_AUTH_HEADER);
return header != null;
}
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
String header = req.getHeader(SecurityUtils.REQUEST_AUTH_HEADER);
// 执行登录时需要传入token,因此要传入一个AuthenticationToken对象
JWTTokenDto jwtTokenDto = new JWTTokenDto();
jwtTokenDto.setToken(header);
getSubject(request,response).login(jwtTokenDto);
return true;
}
/**
* 是否允许登录
* @param request
* @param response
* @param mappedValue
* @return
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
boolean loginAttempt = isLoginAttempt(request, response);
if (loginAttempt){
executeLogin(request,response);
return true;
}
// 如果未登录状态,则返回401错误
response401(response);
return true;
}
private Boolean response401(ServletResponse response){
HttpServletResponse res = (HttpServletResponse) response;
res.setStatus(HttpStatus.UNAUTHORIZED.value());
res.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=utf-8");
PrintWriter out = null;
try {
out = res.getWriter();
out.append(JSON.toJSONString(ResponseFactory.getError(ResponseCode.please_login)));
out.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
if (out != null){
out.close();
}
}
return false;
}
}
2)登录的方式是使用token进行登录,因此需要定义token类
public class JWTTokenDto implements AuthenticationToken {
private String token;
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
3、配置shiro拦截规则
shiro规则使用@Configuration注解,作为组件在启动时配置,加载以下三个部件:
①shiroFilter定义的URI过滤规则
②securityManager定义的安全会话规则
③指定JWTRealm()实现的鉴权规则
@Configuration
public class ShiroConfig {
/**
* 定义权限,启动时加载shiro拦截器
* @Qualifier注解用来指定需要的bean
* @param manager
* @return
*/
@Bean(name="shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager")SecurityManager manager){
// 定义拦截规则,此处加载了https的规则、以及JWT使用token进行验证的规则
Map<String, Filter> filterMap = new HashMap<>();
// https的规则,定义过滤器名称为ssl
filterMap.put("ssl",new SslFilter());
// 配置规则名称为jwt,定义过滤器名称为jwt
filterMap.put("jwt",new JWTFilter());
// 设定不需要拦截的规则,加入到这个规则的URI不会拦截
LinkedHashMap<String,String> filterRules = new LinkedHashMap<>();
// 匹配规则:模糊匹配用**,anon代表匿名访问,用anon标记规则不需要身份验证
filterRules.put("/swagger**","anon");
filterRules.put("/swagger-ui/**","anon");
// 把登录接口驾到过滤规则中,不然登录也会提示401
filterRules.put("/api/auth/tokens","anon");
filterRules.put("/api/auth/regist","anon");
filterRules.put("/api/auth/401","anon");
// 设置需要拦截的规则,所有接口都会被拦截,拦截规则为jwt规则,即前面定义的JWTFilter()
filterRules.put("/**","jwt");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setFilters(filterMap);
// 设置安全事务管理器,使用@Qualifier("securityManager")进行了指定
shiroFilterFactoryBean.setSecurityManager(manager);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRules);
// 设置login地址
shiroFilterFactoryBean.setLoginUrl("/course/login");
// 设置没有登录的地址
shiroFilterFactoryBean.setUnauthorizedUrl("/course/401");
return shiroFilterFactoryBean;
}
// 配置核心安全事务管理器
@Bean(name = "securityManager")
public SecurityManager securityManager(){
// 禁用会话存储,意味着shiro不会把用户的会话信息存储在内存中
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
// 设置SubjectDAO禁用会话存储,Subject代表当前安全上下文,DAO代码访问安全信息的接口
DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
defaultSubjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setSubjectDAO(defaultSubjectDAO);
// jwtRealm用于处理JWT的认证和授权,setRealm后会让shiro用该realm进行身份认证
manager.setRealm(jwtRealm());
return manager;
}
@Bean(name = "jwtRealm")
public JWTRealm jwtRealm(){
JWTRealm jwtRealm = new JWTRealm();
return jwtRealm;
}
}
4、配置AuthorizingRealm鉴权规则
AuthorizingRealm中的doGetAuthenticationInfo,主要用途是实现具体的身份验证逻辑。当用户尝试登录或访问需要身份验证的资源时,框架会调用这个方法来验证用户的身份。
supports则用来定义doGetAuthenticationInfo是否执行,supports返回true则执行,否则不执行。
public class JWTRealm extends AuthorizingRealm {
@Autowired
private UserServiceImp userService;
@Override
public boolean supports(AuthenticationToken token) {
// token是否属于JWTTokenDto,属于则启用下面的鉴权规则
return token instanceof JWTTokenDto;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 从uri中取出token
String token = (String)authenticationToken.getCredentials();
// 从token中取出username
String userAccount = JWTUtils.getUserAccount(token);
// 从数据库查询该用户名是否存在
UserEntity user = userService.getUserByAccount(userAccount);
if (user == null){
throw new ServiceException(ResponseCode.please_login);
}
// 判断用户状态是否已经停用
if ("-1".equals(String.valueOf(user.getUserStatus()))){
throw new ServiceException(ExceptionCodeEnum.user_disable);
}
// 判断用户名密码错误
if (!JWTUtils.verify(token,userAccount,user.getPassword())){
throw new ServiceException(ExceptionCodeEnum.user_password_error);
}
SimpleAuthenticationInfo jwtRealm = new SimpleAuthenticationInfo(token, token, "JwtRealm");
return jwtRealm;
}
}
代码中调用了JWTUtils.getUserAccount、JWTUtils.verify,代码如下:
public static String getUserAccount(String token){
try {
// 解码token获得用户名
DecodedJWT decode = JWT.decode(token);
// claim是token中的声明信息
return decode.getClaim(SecurityUtils.ACCOUNT).asString();
} catch (JWTDecodeException exception){
// 解析失败,返回空
return null;
}
}
public static boolean verify(String token,String account,String pwd){
try{
// 使用HMAC256算法创建一个加密算法对象,密钥为传入的密码pwd。
Algorithm algorithm = Algorithm.HMAC256(pwd);
// 使用当前用户名和算法对象,构建一个JWT验证器,用来验证传入的token
JWTVerifier jwtVerifier = JWT.require(algorithm).withClaim(SecurityUtils.ACCOUNT, account).build();
// verify(token)方法会检查令牌的签名、过期时间以及声明(Claim)
// 如果一切正常,会返回一个DecodedJWT对象,如果验证失败,会抛出相应的异常。
DecodedJWT verify = jwtVerifier.verify(token);
return true;
}catch (Exception e){
return false;
}
}
标签:return,JWT,public,token,new,response,鉴权,Shiro
From: https://blog.csdn.net/weixin_39477393/article/details/143643808