Jwt+shiro实现登录

大致内容 用到下面五个类

在这里插入图片描述

CustomRealm

public class CustomRealm extends AuthorizingRealm {

    @Resource
    private UserService userService;

    /**
     * 限定这个realm只能处理JwtToken
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 授权(授权部分这里就省略了)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
        return simpleAuthorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //JwtToken中重写了这个方法了
        // 这里的 token是从 JwtFilter 的 executeLogin 方法传递过来的,已经经过了解密
        String token = (String) authenticationToken.getCredentials();
        //验证toke是否过期 {如果后期使用redis 存放token可以改成redis判断}
        Assert.isTrue(!JwtUtil.isExpire(token),"token失效,请重新登录");
        //通过token获取用户名
        String username = JwtUtil.getUsername(token);
        //判断用户是否存在
        Assert.notNull(username,"用户不存在");
        //根据用户名获取用户信息
        User user = userService.findUserByUserName(username);
        //密码验证
        Assert.isTrue(JwtUtil.verifyToken(token,username,user.getPassword()),"密码不正确");
        return new SimpleAuthenticationInfo(user, token, this.getName());
    }
}

JwtFilter

@Log4j2
public class JwtFilter extends BasicHttpAuthenticationFilter {

    //前端命名Authorization
    private static final String TOKEN = "Authentication";

    /**
     * shiro url路径匹配
     */
    private AntPathMatcher pathMatcher = new AntPathMatcher();


    /**
     * 返回给你前端提示信息
     */
    private void ResponseOut(ServletResponse response, Map<String, Object> map) throws IOException {
        String json = JsonUtil.toJSONString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
    }

    /**
     * 验证token无效 过去过期
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        //在请求头中获取token
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader(TOKEN);
        if (StringUtils.isBlank(token)) {
            HashMap<String, Object> hashMap = new HashMap<>(2);
            hashMap.put("mag","无token,无权访问,请先登录");
            ResponseOut(response,hashMap);
            return false;
        }

        //token存在,进行验证 ,{如果token存在加密 JwtToken jwtToken = new JwtToken(解密的token)}
        JwtToken jwtToken = new JwtToken(token);
        try {
            //SecurityUtils 全局安全工具类
            //通过subject,提交给CustomRealm进行登录验证 如果验证不通过 会报异常
            SecurityUtils.getSubject().login(jwtToken);
            return true;
        } catch (ExpiredCredentialsException e) {
            HashMap<String, Object> hashMap = new HashMap<>(2);
            hashMap.put("mag","token过期,请重新登录");
            ResponseOut(response,hashMap);
            log.error("token过期,请重新登录:{}",ExceptionUtil.stacktraceToString(e));
            return false;
        } catch (ShiroException e){
            HashMap<String, Object> hashMap = new HashMap<>(2);
            hashMap.put("mag","token无效");
            ResponseOut(response,hashMap);
            log.error("token无效:{}",ExceptionUtil.stacktraceToString(e));
            return false;
        }

    }

    /**
     * 除了shiro 不限制的路由, 前端发送请求 优先进入该方法
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @SneakyThrows
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        boolean result = false;
//        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
//        List<String> urlList = new ArrayList<>();
//        urlList.add("/api/sys/user/login");
//        urlList.add("/api/sys/user/register");
//        //判断是否有过滤的路由
//        for (String url : urlList) {
//            if (pathMatcher.match(url,httpServletRequest.getRequestURI())) {
//                result = true;
//            }
//        }
//        if(result){
//            return  false;
//        }
//        if(isLoginAttempt(request,response)){
//            result = executeLogin(request,response);
//        }
        result = executeLogin(request,response);
        return result;
    }

    /**
     * 判断登录是否带了Token
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request,ServletResponse response){
        boolean result = false;
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        if(httpServletRequest.getHeader(TOKEN)!=null){
            result = true;
        }
        return result;
    }


    /**
     * 处理跨域
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
//        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-control-Allow-Origin", "*");
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));

        boolean result = false;
        // 跨域时会首先发送一个 option请求,这里我们给 option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
        } else {
            result = super.preHandle(request, response);
        }
        return result;
    }

}

JwtToken

/**
 * 自定义的shiro接口token,可以通过这个类将string的token转型成AuthenticationToken,可供shiro使用
 *  注意:需要重写getPrincipal和getCredentials方法,因为是进行三件套处理的,没有特殊配置shiro无法通过这两个
 *  方法获取到用户名和密码,需要直接返回token,之后交给JwtUtil去解析获取。
 *  (当然了,可以对realm进行配置HashedCredentialsMatcher,这里就不这么处理了)
 *  @ClassName : JwtToken
 * @Author : zyg
 * @Date: 2022/4/17 23:42
 */
@Data
public class JwtToken implements AuthenticationToken {

    private String token;

    public JwtToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

JwtUtil

/**
 * Jwt工具类
 * @author zyg
 */
@Slf4j
public class JwtUtil {
    /**
     * 默认签名
     */
    private static final String SING = "!@#$%^&*A1D2FRT5";

    /**
     * 转换成毫秒
     */
    private static final long EXPIRE_TIME= 7200 * 1000;

    /**
     * 根据jwt token获取用户名
     * @param token
     * @return
     */
    public static String getUsername(String token){
        String result = null;
        try{
            DecodedJWT jwt = JWT.decode(token);
            result = jwt.getClaim("username").asString();
        }catch (JWTDecodeException e){
            log.error("error {}",e.getMessage());
        }
        return result;
    }

    /**
     * 根据jwt token 获取用户id
     * @param token
     * @return
     */
    public static Long getUserId(String token){
        Long userId = null;
        try{
            DecodedJWT jwt = JWT.decode(token);
            userId = jwt.getClaim("userId").asLong();
        }catch (JWTDecodeException e){
            log.error("error {}",e.getMessage());
        }
        return userId;
    }

    /**
     * 获取token
     * @param username 用户名称
     * @param password 密码
     * @return
     */
    public static String getJwtToken(String username,Long userId,String password){
       try{
           return JWT.create()
                   //存放信息 后面可以根据name取出
                   .withClaim("userId",userId)
                   .withClaim("username",username)
                   //过期时间
                   .withExpiresAt(new Date(System.currentTimeMillis()+EXPIRE_TIME))
                    //设置签名
                   .sign(Algorithm.HMAC256(ObjectUtil.isNull(password)?SING:password));
       }catch (Exception e){
           log.error("error:{}", ExceptionUtil.stacktraceToString(e));
           return null;
       }
    }

    /**
     * 验证token
     * 如果token验证不通过,则会报异常
     * @param token
     * @param username
     * @param password
     */
    public static boolean verifyToken(String token,String username, String password) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(StrUtil.isBlank(password) ? SING : password);
            JWT.require(algorithm)
                    .withClaim("username", username)
                    .build()
                    .verify(token);
            return true;
        } catch (Exception e) {
            log.error("error:{}", ExceptionUtil.stacktraceToString(e));
        }
        return false;
    }

    /**
     * 校验token是否过期
     * @param token
     * @return
     */
    public static boolean isExpire(String token){
        DecodedJWT jwt = JWT.decode(token);
        return jwt.getExpiresAt().getTime() < DateUtil.current();
    }
}

ShiroConfig

@Configuration
public class ShiroConfig {


    /**
     * 身份验证Realm(账号密码校验,以及授权)
     * @return
     */
    @Bean
    public CustomRealm customRealm(){
        return new CustomRealm();
    }

    /**
     * 创建安全管理器(会自动设置到SecurityUtils中设置这个安全管理器)
     * @return
     */
    @Bean
    public SecurityManager securityManager(){
        //创建安全管理对象
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //给安全管理器设置realm
        securityManager.setRealm(customRealm());
        return securityManager;
    }

    /**
     * 此类在启动的时候会被加载
     * 创建ShiroFilter(用于拦截所有请求,对受限资源进行Shiro的认证和授权判断)
     * Shiro提供了丰富的过滤器(anon等),不过在这里就需要加入我们自定义的JwtFilter
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
        // 设置 securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 添加自己的过滤器并且取名为jwt
        LinkedHashMap<String, Filter> filters = new LinkedHashMap<>();
        filters.put("jwt",new JwtFilter());
        shiroFilterFactoryBean.setFilters(filters);

        LinkedHashMap<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
        //登录路径、注册路径都需要放行不进行拦截
        filterChainDefinitionMap.put("/api/sys/user/login", "anon");
        // 表示对所有资源起作用,jwt过滤器 对资源进行拦截
        filterChainDefinitionMap.put("/api/**","jwt");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 开启对 Shiro 注解的支持
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

测试

  • User
@Data
public class User {
    private Long id;
    private String username;
    private String password;
}
  • UserController
@Slf4j
@RestController
@RequestMapping("/api/sys/user")
public class UserController {

    @Resource
    private UserService userService;

    @GetMapping("/login")
    public Map<String,Object> login(String userName, String passWord) {
        return userService.login(userName,passWord);
    }

    @GetMapping("/page")
    public Map<String,Object> list() {
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("username","admin");
        return hashMap;
    }
}
  • UserService
public interface UserService {

    /**
     * 用户过账号获取用户信息
     * @param username
     * @return
     */
    User findUserByUserName(String username);

    /**
     * 登录
     * @param userName
     * @param passWord
     * @return
     */
    Map<String, Object> login(String userName, String passWord);
}
  • UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
    @Override
    public User findUserByUserName(String username) {
        if ("admin".equals(username)) {
            User user = new User();
            user.setUsername("admin");
            user.setPassword("123456");
            user.setId(Long.valueOf(123456789));
            return user;
        }
        return null;
    }

    @Override
    public Map<String, Object> login(String userName, String passWord) {
        if ("admin".equals(userName)) {
            User user = new User();
            user.setUsername("admin");
            user.setPassword("123456");
            user.setId(Long.valueOf(123456789));
            //获取到token字符串
            String token = JwtUtil.getJwtToken(user.getUsername(), user.getId(), user.getPassword());
            //转换成jwtToken (才可以被shiro识别)
            JwtToken jwtToken = new JwtToken(token);
            Map<String, Object> hashMap = new HashMap<>();
            hashMap.put("token",jwtToken.getToken());
            return hashMap;
        }
        return null;
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
可以的,我可以为您提供一个基于Spring Boot、JWTShiro和Redis的例子。这个例子将演示如何使用这些技术实现用户认证和授权,并且将用户的状态存储在Redis中。 首先,您需要创建一个Spring Boot项目并添加所需的依赖。 在pom.xml文件中添加以下依赖: ``` <dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.7.1</version> </dependency> <!-- JWT --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <!-- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> ``` 接下来,创建一个名为`JwtUtils`的JWT工具类,用于生成和验证JWT令牌。您可以参考以下代码: ```java import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.Date; @Component public class JwtUtils { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private int expiration; @PostConstruct public void init() { secret = Base64.getEncoder().encodeToString(secret.getBytes()); } public String generateToken(String username) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + expiration * 1000); return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public String getUsernameFromToken(String token) { Claims claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); return claims.getSubject(); } public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(secret).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } } ``` 然后,创建一个名为`RedisUtils`的Redis工具类,用于操作Redis。您可以参考以下代码: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; @Component public class RedisUtils { @Autowired private RedisTemplate<String, Object> redisTemplate; public void set(String key, Object value, long expiration) { redisTemplate.opsForValue().set(key, value, expiration, TimeUnit.SECONDS); } public Object get(String key) { return redisTemplate.opsForValue().get(key); } public void delete(String key) { redisTemplate.delete(key); } public boolean hasKey(String key) { return redisTemplate.hasKey(key); } } ``` 接下来,创建一个名为`JwtRealm`的Shiro Realm,用于验证JWT令牌和授权。您可以参考以下代码: ```java import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; public class JwtRealm extends AuthorizingRealm { @Autowired private JwtUtils jwtUtils; @Autowired private RedisUtils redisUtils; @Override public boolean supports(AuthenticationToken token) { return token instanceof JwtToken; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // TODO: 实现授权逻辑 } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { JwtToken jwtToken = (JwtToken) token; String username = jwtUtils.getUsernameFromToken(jwtToken.getToken()); if (username == null || !jwtUtils.validateToken(jwtToken.getToken())) { throw new AuthenticationException("无效的令牌"); } // TODO: 查询用户信息并返回认证信息 return new SimpleAuthenticationInfo(username, jwtToken.getToken(), getName()); } } ``` 最后,创建一个名为`JwtToken`的Shiro Token,用于封装JWT令牌。您可以参考以下代码: ```java import org.apache.shiro.authc.AuthenticationToken; public class JwtToken implements AuthenticationToken { private String token; public JwtToken(String token) { this.token = token; } @Override public Object getPrincipal() { return token; } @Override public Object getCredentials() { return token; } } ``` 以上是一个基于Spring Boot、JWTShiro和Redis的例子。您可以根据您的需求进行修改和扩展。希望对您有帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值