Shiro框架(3)---整合shiro+jwt

有道云:有道云笔记

JWT

JSON Web Token(JSON Web令牌)

是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密(使用HNAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加

密、签名等相关处理。

JWT的作用:

1.授权:一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。它的开销很小并且可以在不同的域中使用。如:单点登录。

2.信息交换:在各方之间安全地传输信息。JWT可进行签名(如使用公钥私钥对),因此可确保发件人。由于签名是使用标头和有改负载计算的,因此还可验证内容是否被篡改。

传统session的验证方式:

http协议本身是一种无状态的协议,如果用户向服务器提供了用户名和密码来进行用户认证,下次请求时,用户还要再一次进行用户认证才行。因为根据htp协议,服务器并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样应用就能识别请求来自哪个用户。

暴露的问题:

1.·用户经过应用认证后,应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大;

2.用户认证后,服务端做认证记录,如果认证的记录被保存在内存中的话,用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源。在分布式的应用上,限制了负载均衡器的能力。以此限制了应用的扩展能力;

3.session是基于cookie来进行用户识别,cookie如果被截获,用户很容易受到CSRF(跨站伪造请求攻击)攻击

4.在前后端分离系统中应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次。如果用session每次携带sessionid到服务器,服务器还要查询用户信息,同时如果用户很多。这些信息存储在服务器内存中,给服务器增加负担。还有就是sessionid就是-个特征值,表达的信息不够丰富。不容易扩展。而且如果你后端应用是多节点部署。那么就需要实现session共享机制。不方便集群应用。

JWT认证

一、认证流程

1.前端通过Web表单将自己的用户名和密码发送到后端的接口。该过程一般是HTTP的POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。

2.后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。

3.后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage(浏览器本地缓存)或sessionStorage(session缓存)上,退出登录时前端删除保存的JWT即可。

4.前端在每次请求时将JWT放入HTTP的Header中的Authorization位。(解决XSS和XSRF问题)HEADER

5.后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确﹔检查Token是否过期;检查Token的接收方是否是自己(可选)

6.验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

二、JWT优点

1.简洁(Compact):可以通过URL,POST参数或者在HTTP header发送,数据量小,传输速度也很快;

2.自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库;

3.Token是以JSON加密的形式保存在客户端,所以JWT是跨语言的,原则上任何web形式都支持。

4.不需要在服务端保存会话信息,特别适用于分布式微服务。

三、JWT结构

就是令牌token,是一个String字符串,由3部分组成,中间用点隔开

令牌组成:

  1. 标头(Header)
  2. 有效载荷(Payload)
  3. 签名(Signature)

token格式:head.payload.singurater 如:xxxxx.yyyy.zzzz

Header:有令牌的类型和所使用的签名算法,如HMAC、SHA256、RSA;使用Base64编码组成;(Base64是一种编码,不是一种加密过程,可以被翻译成原来的样子)

{ "alg" : "HS256", "type" : "JWT" }

Payload :有效负载,包含声明;声明是有关实体(通常是用户)和其他数据的声明,不放用户敏感的信息,如密码。同样使用Base64编码

{ "sub" : "123", "name" : "John Do", "admin" : true }

Signature :前面两部分都使用Base64进行编码,前端可以解开知道里面的信息。Signature需要使用编码后的header和payload加上我们提供的一个密钥,使用header中指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret);

**签名目的:**签名的过程实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

信息安全问题:Base64是一种编码,是可逆的,适合传递一些非敏感信息;JWT中不应该在负载中加入敏感的数据。如传输用户的ID被知道也是安全的,如密码不能放在JWT中;JWT常用于设计用户认证、授权系统、web的单点登录。

示例:spingboot3.0系列示例代码采用3.1.0版本,jdk版本使用17+

配置文件

spring.application.name=springboot3-shiro-jwt spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql:///shiroDB spring.datasource.username=root spring.datasource.password=sasa mybatis-plus.mapper-locations=classpath*:com/lfz/shiro_jwt/mapper/*Mapper.xml mybatis-plus.type-aliases-package=com.lfz.shiro_jwt.entity mybatis-plus.configuration.auto-mapping-behavior=full mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl logging.level.com.lfz.shiro_jwt=debug

注意:

  1. 虽然最新版本的shiro适配了springboot3,但部分核心包(shiro-core、shiro-web)仍需要单独适配jakarta。
  2. 注意java-jwt、jjwt版本号,太低版本不支持springboot3。

引入pom依赖

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.3.0</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.3</version> </dependency> <!--fastjson--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.40</version> </dependency> <!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <classifier>jakarta</classifier> <version>1.12.0</version> <!-- 排除仍使用了javax.servlet的依赖 --> <exclusions> <exclusion> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> </exclusion> <exclusion> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> </exclusion> </exclusions> </dependency> <!-- 引入适配jakarta的依赖包 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <classifier>jakarta</classifier> <version>1.12.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <classifier>jakarta</classifier> <version>1.12.0</version> <exclusions> <exclusion> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> </exclusion> </exclusions> </dependency> <!-- jjwt依赖包 --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.4.0</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.12.1</version> </dependency>

过滤静态资源:

<resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources>

生成实体类、Mapper类、Service类

编写UserService,根据用户名称查询用户权限的方法。

【复制上一次课,写了roel,和Authority的两个查询方法】

创建响应码枚举

com.lfz.shiro_jwt.shiro.enums.ResponseCodeEnum

package com.lfz.shiro_jwt.shiro.enums; /** * 响应状态码 */ public enum ResponseCodeEnum { OK(200, "请求成功"), BAD_REQUEST(400, "失败的请求"), UNAUTHORIZED(401, "未授权"), FORBIDDEN(403, "禁止访问"), NOT_FOUND(404, "请求找不到"), NOT_ACCEPTABLE(406, "不可访问"), CONFLICT(409, "冲突"), ERROR(500, "服务器发生异常"); private final Integer code; private final String message; ResponseCodeEnum(Integer code, String message) { this.code = code; this.message = message; } public Integer getCode() { return code; } public String getMessage() { return getMessage(); } }

创建用户登陆请求实体

com.lfz.shiro_jwt.entity.request.UserLoginDTO

package com.lfz.shiro_jwt.entity.request; import lombok.Data; import java.io.Serializable; /** * 用户登陆请求实体 */ @Data public class UserLoginDTO implements Serializable { private static final long serialVersionUID = -1L; private String username; private String password; }

接口响应实体

com.lfz.shiro_jwt.entity.request.ResultDTO

package com.lfz.shiro_jwt.entity.response; import com.lfz.shiro_jwt.shiro.enums.ResponseCodeEnum; import lombok.Data; import java.io.Serializable; /** * 接口响应实体 */ @Data public class ResultDTO implements Serializable { private static final long serialVersionUID = -1L; /** * 响应状态码 */ private Integer code; /** * 响应信息 */ private String message; /** * 响应数据 */ private Object data; public static ResultDTO success() { return success("ok"); } public static ResultDTO success(String message) { return success(message, null); } public static ResultDTO success(Object data) { return success("ok", data); } public static ResultDTO success(String message, Object data) { ResultDTO resultDTO = new ResultDTO(); resultDTO.setCode(ResponseCodeEnum.OK.getCode()); resultDTO.setMessage(message); resultDTO.setData(data); return resultDTO; } public static ResultDTO error(String message) { return error(ResponseCodeEnum.ERROR, message); } public static ResultDTO error(ResponseCodeEnum responseCode, Throwable e) { return error(responseCode, e.getMessage() != null ? e.getMessage() : "系统异常,请联系管理员!"); } public static ResultDTO error(ResponseCodeEnum responseCode, String message) { ResultDTO resultDTO = new ResultDTO(); resultDTO.setCode(responseCode.getCode()); resultDTO.setMessage(message); return resultDTO; } }

创建统一异常处理

com.hexadecimal.example.exception.BaseException

package com.hexadecimal.example.exception; import com.hexadecimal.example.enums.ResponseCodeEnum; import lombok.Data; import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode(callSuper = true) public class BaseException extends RuntimeException { private ResponseCodeEnum responseCode; public BaseException(ResponseCodeEnum responseCode, String message) { super(message); setResponseCode(responseCode); } }

创建全局异常处理

com.hexadecimal.example.exception.handler.BaseExceptionHandler

package com.hexadecimal.example.exception.handler; import com.hexadecimal.example.enums.ResponseCodeEnum; import com.hexadecimal.example.exception.BaseException; import com.hexadecimal.example.res.ResultDTO; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * 全局异常处理类 */ @RestControllerAdvice @Slf4j public class BaseExceptionHandler { /** * 捕捉shiro的异常 * * @param e * @return */ @ExceptionHandler(ShiroException.class) public ResultDTO handle401(ShiroException e) { log.debug("捕捉到shiro的异常",e.getMessage()); return ResultDTO.error(ResponseCodeEnum.UNAUTHORIZED, "未授权"); } /** * 捕捉UnauthorizedException * * @return */ @ResponseStatus(HttpStatus.UNAUTHORIZED) @ExceptionHandler(AuthenticationException.class) public ResultDTO handle401() { log.debug("捕捉UnauthorizedException的异常"); return ResultDTO.error(ResponseCodeEnum.UNAUTHORIZED, "未登录"); } /** * 处理BindException * * @param e * @return */ @ExceptionHandler(BindException.class) public ResultDTO handlerBindException(BindException e) { log.error("请求异常:", e.getMessage()); BindingResult bindingResult = e.getBindingResult(); FieldError fieldError = bindingResult.getFieldError(); assert fieldError != null; String defaultMessage = fieldError.getDefaultMessage(); return ResultDTO.error(ResponseCodeEnum.BAD_REQUEST, defaultMessage); } /** * 处理Exception * * @param e * @return */ @ExceptionHandler(Exception.class) public ResultDTO handlerException(Exception e) { log.error("请求异常:", e.getMessage()); return ResultDTO.error(ResponseCodeEnum.ERROR, e); } }

集成jwt

1.创建工具类JwtUtil

com.lfz.shiro_jwt.shiro.jwt.util.JwtUtil

package com.hexadecimal.example.jwt.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.DecodedJWT; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.Date; import java.util.Map; @Component @Slf4j public class JwtUtil { private static final String SECRET = "SecretKey012345678901234567890123456789012345678901234567890123456789"; private static final long EXPIRE = 60 * 24 * 7; public static final String HEADER = "Authorization"; /** * 生成jwt token */ public String generateToken(String username) { SecretKey signingKey = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8)); //过期时间 LocalDateTime tokenExpirationTime = LocalDateTime.now().plusMinutes(EXPIRE); return Jwts.builder() .signWith(signingKey, Jwts.SIG.HS512) .header().add("typ", "JWT").and() .issuedAt(Timestamp.valueOf(LocalDateTime.now())) .subject(username) .expiration(Timestamp.valueOf(tokenExpirationTime)) .claims(Map.of("username", username)) .compact(); } public Claims getClaimsByToken(String token) { SecretKey signingKey = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8)); return Jwts.parser() .verifyWith(signingKey) .build() .parseSignedClaims(token) .getPayload(); } /** * 检查token是否过期 * * @return true:过期 */ public boolean isTokenExpired(Date expiration) { return expiration.before(new Date()); } /** * 获得token中的自定义信息,一般是获取token的username,无需secret解密也能获得 * @param token * @param filed * @return */ public String getClaimFiled(String token, String filed){ try{ DecodedJWT jwt = JWT.decode(token); return jwt.getClaim(filed).asString(); } catch (JWTDecodeException e){ log.error("JwtUtil getClaimFiled error: ", e); return null; } } public static void main(String[] args) { JwtUtil jwtUtil = new JwtUtil(); String token = jwtUtil.generateToken("admin"); System.out.println("token = " + token); Claims claims = jwtUtil.getClaimsByToken(token); System.out.println("claims = " + claims); String username = jwtUtil.getClaimFiled(token, "username"); System.out.println("username = " + username); } }

2.创建JwtToken存储用户/令牌

com.lfz.shiro_jwt.shiro.jwt.model.JwtToken

package com.lfz.shiro_jwt.shiro.jwt.model; import com.lfz.shiro_jwt.shiro.jwt.util.JwtUtil; import org.apache.shiro.authc.AuthenticationToken; /** * 继承AuthenticationToken,跟AccountRealmh中的doGetAuthenticationInfo的参数类型保持一致 */ public class JwtToken implements AuthenticationToken { private String username; private String token; public JwtToken(String token){ this.token = token; JwtUtil jwtUtil = new JwtUtil(); this.username = jwtUtil.getClaimFiled(token, "username"); } /** * 类似用户名 * @return */ @Override public Object getPrincipal() { return username; } /** * 类似密码 * @return */ @Override public Object getCredentials() { return token; } }

3.创建JwtFilter过滤器

com.lfz.shiro_jwt.shiro.jwt.filter.JwtFilter

package com.lfz.shiro_jwt.shiro.jwt.filter; import com.lfz.shiro_jwt.shiro.jwt.model.JwtToken; import com.lfz.shiro_jwt.shiro.jwt.util.JwtUtil; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.web.filter.AccessControlFilter; import org.springframework.stereotype.Component; import java.io.IOException; @Slf4j @Component public class JwtFilter extends AccessControlFilter { /** * isAccessAllowed()判断是否携带了有效的JwtToken * onAccessDenied()是没有携带JwtToken的时候进行账号密码登录,登录成功允许访问,登录失败拒绝访问 */ @Override protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception { /** * 1. 返回true,shiro就直接允许访问url * 2. 返回false,shiro才会根据onAccessDenied的方法的返回值决定是否允许访问url * 这里先让它始终返回false来使用onAccessDenied()方法 */ log.info("isAccessAllowed方法被调用"); return false; } /** * @param servletRequest * @param servletResponse * @throws Exception * @return 返回结果为true表明登录通过 */ @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { /** * 跟前端约定将jwtToken放在请求的Header的Authorization中,Authorization:token */ log.info("onAccessDenied方法被调用"); HttpServletRequest request = (HttpServletRequest) servletRequest; String token = request.getHeader(JwtUtil.HEADER); //如果token为空的话,返回true,交给控制层@RequiresAuthentication进行判断;也会达到没有权限的作用 if (token == null) { return true; } JwtToken jwtToken = new JwtToken(token); try { //进行登录处理,委托realm进行登录认证,调用AccountRealm进行的认证 getSubject(servletRequest, servletResponse).login(jwtToken); } catch (Exception e) { log.error("Subject login error:", e); return false; } //如果走到这里,那么就返回true,代表登录成功 return true; } //登录失败要执行的方法 private void onLoginFail(ServletResponse response) throws IOException { HttpServletResponse httpServletResponse = (HttpServletResponse) response; httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); httpServletResponse.getWriter().print("login error"); } }

4.创建AccountRealm做认证/授权

com.lfz.shiro_jwt.shiro.jwt.realm.AccountRealm

package com.lfz.shiro_jwt.shiro.jwt.realm; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.lfz.shiro_jwt.entity.User; import com.lfz.shiro_jwt.service.impl.UserService; import com.lfz.shiro_jwt.shiro.enums.ResponseCodeEnum; import com.lfz.shiro_jwt.shiro.exception.BaseException; import com.lfz.shiro_jwt.shiro.jwt.model.JwtToken; import com.lfz.shiro_jwt.shiro.jwt.util.JwtUtil; import io.jsonwebtoken.Claims; import jakarta.annotation.Resource; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class AccountRealm extends AuthorizingRealm { @Resource private JwtUtil jwtUtil; @Autowired private UserService userService; @Autowired private RoleService roleService; @Autowired private AuthorityService authorityService; /** * 多重写一个support * 标识这个Realm是专门用来验证JwtToken * 不负责验证其他的token(UsernamePasswordToken) */ @Override public boolean supports(AuthenticationToken token) { return token instanceof JwtToken; } /** * 认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String jwt = (String) authenticationToken.getCredentials(); // 获取jwt中关于用户名 String username = jwtUtil.getClaimsByToken(jwt).getSubject(); // 查询用户 User user = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getUsername, username)); if (user == null) { throw new BaseException(ResponseCodeEnum.BAD_REQUEST, "用户不存在"); } if (user.getIsDeleted()==1) { throw new BaseException(ResponseCodeEnum.BAD_REQUEST, "用户被锁定"); } Claims claims = jwtUtil.getClaimsByToken(jwt); if (jwtUtil.isTokenExpired(claims.getExpiration())) { throw new BaseException(ResponseCodeEnum.BAD_REQUEST, "token过期,请重新登录"); } return new SimpleAuthenticationInfo(user, jwt, getName()); } /** * 授权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { User user = (User) principalCollection.getPrimaryPrincipal(); List<Role> roleList=user!=null?roleService.getRolesByUserId(user.getId()):null; //授权角色信息 if(!CollectionUtils.isEmpty(roleList)){ SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo(); //遍历角色列表 roleList.forEach(role -> { //添加角色信息 simpleAuthorizationInfo.addRole(role.getName()); //获取该角色权限列表 List<Authority> authorityList=authorityService.getAuthoritiesByRoleId(role.getId()); if(!CollectionUtils.isEmpty(authorityList)){ authorityList.forEach(authority -> { //添加权限信息 simpleAuthorizationInfo.addStringPermission(authority.getName()); }); } }); System.out.println("==========返回权限信息========="); System.out.println("用户角色:"); System.out.println(simpleAuthorizationInfo.getRoles()); System.out.println("用户权限信息"); System.out.println(simpleAuthorizationInfo.getStringPermissions()); return simpleAuthorizationInfo; } return null; } }

shiro配置

com.lfz.shiro_jwt.shiro.config.ShiroConfig

package com.lfz.shiro_jwt.shiro.config; import com.lfz.shiro_jwt.shiro.jwt.filter.JwtFilter; import com.lfz.shiro_jwt.shiro.jwt.realm.AccountRealm; import jakarta.servlet.Filter; import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; import org.apache.shiro.mgt.DefaultSubjectDAO; 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.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; 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.HashMap; import java.util.LinkedHashMap; @Configuration public class ShiroConfig { @Bean("securityManager") public DefaultWebSecurityManager securityManager(@Qualifier("accountRealm") AccountRealm accountRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(accountRealm); // 关闭shiroDao功能 DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); // 不需要将ShiroSession中的东西存到任何地方包括Http Session中) defaultSessionStorageEvaluator.setSessionStorageEnabled(false); subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); securityManager.setSubjectDAO(subjectDAO); // securityManager.setSubjectFactory(subjectFactory()); return securityManager; } @Bean("shiroFilterFactoryBean") public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); /** * 注册jwt过滤器,除/login外都先经过jwtFilter */ HashMap<String, Filter> filterMap = new HashMap<>(); filterMap.put("jwt", new JwtFilter()); shiroFilter.setFilters(filterMap); // 拦截器 LinkedHashMap<String, String> map = new LinkedHashMap<>(); map.put("/login", "anon"); map.put("/**", "jwt"); shiroFilter.setFilterChainDefinitionMap(map); return shiroFilter; } /** * 解决@RequiresAuthentication注解不生效的配置 */ @Bean("lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean @DependsOn({"lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } /** * 为Spring-Bean开启对Shiro注解的支持 */ @Bean("authorizationAttributeSourceAdvisor") public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }

测试登录请求:

UserController:

@RestController @RequestMapping(path = "/user", produces = "application/json;charset=utf-8") public class UserController { @Autowired private JwtUtil jwtUtil; @Autowired private UserService userService; @PostMapping("/login") @CrossOrigin public ResultDTO login(@RequestBody @Validated UserLoginDTO userLoginDTO, HttpServletResponse response) { String username = userLoginDTO.getUsername(); String password = userLoginDTO.getPassword(); User user = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getUsername, userLoginDTO.getUsername())); if (user == null) { return ResultDTO.error("用户名不存在");} Md5Hash pwd=new Md5Hash(password,user.getSalt(),1024); if (!user.getPassword().equals(pwd.toString())) { return ResultDTO.error("用户名或密码错误"); } String token = jwtUtil.generateToken(username); response.setHeader(JwtUtil.HEADER, token); response.setHeader("Access-control-Expost-Headers", JwtUtil.HEADER); Map<String, String> map = new HashMap<>(); map.put("token", token); return ResultDTO.success(map); } }

前端jquery测试:

<script> $(function(){ $("button:eq(0)").click(function(){ $.ajax({ url:'http://localhost:8080/user/login', type:'POST', data:JSON.stringify({username:"admin",password:"123456"}), dataType:"json", contentType:"application/json;charset=utf-8", success:function(res){ console.log(res); $("span").text(res.data.token); },error:function(err){ alert(err); } }); }); }) </script>

查询用户列表测试:

//@RequiresAuthentication//必须携带token令牌【认证成功】才能才问 @CrossOrigin //@RequiresRoles("admin") //@RequiresPermissions("admin:user:findUser") @GetMapping("/findUser") public ResultDTO findUser(){ List<User> list=userService.list(); return ResultDTO.success(list); }

$("button:eq(3)").click(function(){ $.ajax({ url:'http://localhost:8080/user/findUser', type:'Get', //headers:{"Authorization":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MDkxMDc2MDksInN1YiI6ImFkbWluIiwiZXhwIjoxNzA5NzEyNDA5LCJ1c2VybmFtZSI6ImFkbWluIn0.6AY5aM-rdh6jL35EIJ_izwHkQc4CcWhTpZNloSJOp8_vFJa7AqFW9AO4DWv2F0qFgWkoTN147395tYKgaU2UBw"}, success:function(res){ console.log(res); } }); });

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值