常见的单token登录方案

news2024/11/26 12:19:08

现在主流的单token方案为jwttoken和redis token

常用的跟jwt token集成框架有shrio、spring security、aop切面。redis也能跟这三者集成。跟redis相比,jwt token比较难注销,得等到有效期过了才行,实际根据项目需求来就行。

简单介绍如下,理论就不讲太多,看代码吧。

一、jwt+shrio实现

1.依赖导入

<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.10.7</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.3</version>
        </dependency>

2.代码编写

工具类JwtOperator.java

package com.vvvtimes.demo.util;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.Map;

@Slf4j
@RequiredArgsConstructor
@SuppressWarnings("WeakerAccess")
@Component
public class JwtOperator {
    /**
     * 秘钥
     * - 默认aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssttt
     */
    @Value("${secret:aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssttt}")
    private String secret;
    /**
     * 有效期,单位秒
     * - 默认2周
     */
    @Value("${expire-time-in-second:1209600}")
    private Long expirationTimeInSecond;

    /**
     * 从token中获取claim
     *
     * @param token token
     * @return claim
     */
    public Claims getClaimsFromToken(String token) {
        try {
            return Jwts.parser()
                .setSigningKey(this.secret.getBytes())
                .parseClaimsJws(token)
                .getBody();
        } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {
            log.error("token解析错误", e);
            throw new IllegalArgumentException("Token invalided.");
        }
    }

    /**
     * 获取token的过期时间
     *
     * @param token token
     * @return 过期时间
     */
    public Date getExpirationDateFromToken(String token) {
        return getClaimsFromToken(token)
            .getExpiration();
    }

    /**
     * 判断token是否过期
     *
     * @param token token
     * @return 已过期返回true,未过期返回false
     */
    private Boolean isTokenExpired(String token) {
        Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    /**
     * 计算token的过期时间
     *
     * @return 过期时间
     */
    private Date getExpirationTime() {
        return new Date(System.currentTimeMillis() + this.expirationTimeInSecond * 1000);
    }

    /**
     * 为指定用户生成token
     *
     * @param claims 用户信息
     * @return token
     */
    public String generateToken(Map<String, Object> claims) {
        Date createdTime = new Date();
        Date expirationTime = this.getExpirationTime();


        byte[] keyBytes = secret.getBytes();
        SecretKey key = Keys.hmacShaKeyFor(keyBytes);

        return Jwts.builder()
            .setClaims(claims)
            .setIssuedAt(createdTime)
            .setExpiration(expirationTime)
            // 你也可以改用你喜欢的算法
            // 支持的算法详见:https://github.com/jwtk/jjwt#features
            .signWith(key, SignatureAlgorithm.HS256)
            .compact();
    }

    /**
     * 判断token是否非法
     *
     * @param token token
     * @return 未过期返回true,否则返回false
     */
    public Boolean validateToken(String token) {
        return !isTokenExpired(token);
    }

    /*public static void main(String[] args) {
        // 1. 初始化
        JwtOperator jwtOperator = new JwtOperator();
        jwtOperator.expirationTimeInSecond = 1209600L;
        jwtOperator.secret = "aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrsssttt";

        // 2.设置用户信息
        HashMap<String, Object> objectObjectHashMap = Maps.newHashMap();
        objectObjectHashMap.put("id", "1");

        // 测试1: 生成token
        String token = jwtOperator.generateToken(objectObjectHashMap);
        // 会生成类似该字符串的内容: eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE1NjU1ODk4MTcsImV4cCI6MTU2Njc5OTQxN30.27_QgdtTg4SUgxidW6ALHFsZPgMtjCQ4ZYTRmZroKCQ
        System.out.println(token);

        // 将我改成上面生成的token!!!
        String someToken = "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE2OTU4OTc2NTQsImV4cCI6MTY5NzEwNzI1NH0.XK730y8tMwAjjTdLqYV4FArwX-_7l0oUvtDmJX2wmLE";
        // 测试2: 如果能token合法且未过期,返回true
        Boolean validateToken = jwtOperator.validateToken(someToken);
        System.out.println(validateToken);

        // 测试3: 获取用户信息
        Claims claims = jwtOperator.getClaimsFromToken(someToken);
        System.out.println(claims);

        // 将我改成你生成的token的第一段(以.为边界)
        String encodedHeader = "eyJhbGciOiJIUzI1NiJ9";
        // 测试4: 解密Header
        byte[] header = Base64.decodeBase64(encodedHeader.getBytes());
        System.out.println(new String(header));

        // 将我改成你生成的token的第二段(以.为边界)
        String encodedPayload = "eyJpZCI6IjEiLCJpYXQiOjE2OTU4OTc2NTQsImV4cCI6MTY5NzEwNzI1NH0";
        // 测试5: 解密Payload
        byte[] payload = Base64.decodeBase64(encodedPayload.getBytes());
        System.out.println(new String(payload));

        // 测试6: 这是一个被篡改的token,因此会报异常,说明JWT是安全的
        jwtOperator.validateToken("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJpYXQiOjE1NjU1ODk3MzIsImV4cCI6MTU2Njc5OTMzMn0.nDv25ex7XuTlmXgNzGX46LqMZItVFyNHQpmL9UQf-aUx");
    }*/
}

配置类ShiroBeanConfig

package com.vvvtimes.demo.auth.config;


import com.vvvtimes.demo.auth.ShiroAuthFilter;
import com.vvvtimes.demo.auth.ShiroAuthRealm;
import org.apache.shiro.mgt.SecurityManager;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * shrio配置类
 */
@Configuration
public class ShiroBeanConfig {

    @Bean("securityManager")
    public SecurityManager securityManager(ShiroAuthRealm shiroAuthRealm){
    //ShiroAuthRealm是自定义reaml
        DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
        manager.setRealm(shiroAuthRealm);
        return manager;
    }
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilter=new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        //装载自定义的token过滤器
        Map<String, Filter> tokenFilterMap=new HashMap<>();
        tokenFilterMap.put("oauth2",new ShiroAuthFilter());
        shiroFilter.setFilters(tokenFilterMap);
        //普通路径过滤规则
        Map<String, String> filterMap = new LinkedHashMap<>();
        //测试相关
        filterMap.put("/test", "anon");

        //登录相关
        filterMap.put("/login/doLogin", "anon");
        filterMap.put("/login/logout", "anon");
        filterMap.put("/login/noLogin", "anon");
        filterMap.put("/login/captcha", "anon");

        //临时测试
        //filterMap.put("/user/addUser", "anon");


        //自定义的token过滤器拦截其他任何请求
        filterMap.put("/**", "oauth2");
        shiroFilter.setFilterChainDefinitionMap(filterMap);
        return shiroFilter;
    }
    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
        proxyCreator.setProxyTargetClass(true);
        return proxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

}

shrio过滤器类

package com.vvvtimes.demo.auth;

import cn.hutool.json.JSONUtil;
import com.vvvtimes.demo.common.dto.RestResponse;
import com.vvvtimes.demo.vo.UserInfo;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Shiro过滤器类
 */
public class ShiroAuthFilter extends AuthenticatingFilter {
    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        String token = getToken((HttpServletRequest) servletRequest);
        if(StringUtils.hasText(token)){
            //ShiroAuthToken是自己定义实现AuthenticationToken接口的token
           return new ShiroAuthToken(token);
        }
        //executeLogin调用该方法token==null就抛异常,我们在onAccessDenied入口
        //重写方法没有token就提前返回并携带原因
        return null;
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return false;
    }

    /**
     * 登录失败,重写方法返回失败原因
     * @param token
     * @param e
     * @param request
     * @param response
     * @return false
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        //重写登录失败,把登录失败的原因返回给前端
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setContentType("application/json;charset=utf-8");
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Allow-Origin", ((HttpServletRequest)request).getHeader("Origin"));

        httpResponse.setCharacterEncoding("utf-8");
        httpResponse.setHeader("ContentType","application/json;charset=utf-8");
        httpResponse.setContentType("application/json;charset=utf-8");

        try {
            //处理登录失败的异常
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            //返回401 new JsonResult.error(throwable.getMessage())

            RestResponse<String> restResponse = new RestResponse<>();
            restResponse.setStatus(401);
            restResponse.setMessage("token校验失败");
            String json = JSONUtil.toJsonStr(restResponse);

            httpResponse.getWriter().print(json);
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        return false;
    }

    /**
     * 程序入口 检验token是否携带,没携带就返回提示没有token
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        //获取用户token
        String token = getToken((HttpServletRequest) servletRequest);
        //如果token不存在提前返回401
        if(!StringUtils.hasText(token)){
            HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-Control-Allow-Origin", ((HttpServletRequest)servletRequest).getHeader("Origin"));
            //new JsonResult.error(invalid token)


            httpResponse.setCharacterEncoding("utf-8");
            httpResponse.setHeader("ContentType","application/json;charset=utf-8");
            httpResponse.setContentType("application/json;charset=utf-8");

            RestResponse<String> restResponse = new RestResponse<>();
            restResponse.setStatus(401);
            restResponse.setMessage("token不存在");
            String json = JSONUtil.toJsonStr(restResponse);

            httpResponse.getWriter().print(json);
            return false;
        }
        //executeLogin方法会校验createToken是否返回了非null AuthenticationToken
        //上面我们提前校验提前返回信息给前端
        return executeLogin(servletRequest, servletResponse);
    }

    // 处理跨域预检请求 preflight
    @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-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个OPTIONS请求,这里我们给OPTIONS请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }


    /**
     * 方法获取用户的token
     * @param request
     * @return token
     */
    public String getToken(HttpServletRequest request){
        //获取请求头
        String token = request.getHeader("token");
        //没有请求头就从参数里拿
        if (!StringUtils.hasText(token)){
            token=request.getParameter("token");
        }
        return token;
    }
}

shrio realm类

package com.vvvtimes.demo.auth;

import com.vvvtimes.demo.mapper.UserMapper;
import com.vvvtimes.demo.util.JwtOperator;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * shrioRealm类
 */
@Component
@Slf4j
public class ShiroAuthRealm extends AuthorizingRealm {

    @Resource
    private UserMapper userMapper;

    @Autowired
    private JwtOperator jwtOperator;
    /**
     * 判断是否支持token的类型 ****important****
     * 每一个Ream都有一个supports方法,用于检测是否支持此Token,默认的采用了return false
     * @param token
     * @return
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof ShiroAuthToken;
    }
    /**
     * 授权 集合会与 @RequiresPermissions()声明的权限方法匹配,通常不调用权限的方法不会执行
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        Map<String, Object> user = (Map<String, Object>) principalCollection.getPrimaryPrincipal();
        log.info("授权方法检索权限:当前的用户="+user);
        String userid =  (String) user.get("id");
        String username = (String) user.get("username");
        //权限集合
        Set<String> permsSet=new HashSet<>();
        //根据用户的信息查权限存入set
        if("123".equals(userid)){
            permsSet.add("sys:user");
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setStringPermissions(permsSet);
        return info;
    }
    /**
     * 认证,登录的时候会调用
     * 调用时机
     * Subject subject = SecurityUtils.getSubject();
     * subject.login(token);
     * 在自定义token过滤器的executeLogin方法也会调用到上面两行,所以也会执行到下面的doGetAuthenticationInfo,每次请求都会认证
     * authenticationToken能强转成字符串,因为用的是自定义的String类型的token
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String token = (String) authenticationToken.getPrincipal();
        //通过token到redis缓存里获取到对应用户user
        Map<String, Object> userEntity=new HashMap<>();
        if(jwtOperator.validateToken(token)){
            Claims claims = jwtOperator.getClaimsFromToken(token);
            userEntity.put("id",claims.get("id"));
            userEntity.put("username",claims.get("username"));
            log.info("认证token");
            //将 token id username 存入上下文

            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

            ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
            HttpServletRequest request = attributes.getRequest();
            request.setAttribute("token",token);
            request.setAttribute("id",claims.get("id"));
            request.setAttribute("username",claims.get("username"));


        }else{
            log.warn("token失效");
            throw new IncorrectCredentialsException("token失效");
        }
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userEntity, token, getName());
        return info;
    }
}

自定义token

package com.vvvtimes.demo.auth;

import org.apache.shiro.authc.AuthenticationToken;

/**
 * 自定义token,在自定义token过滤器里使用
 */
public class ShiroAuthToken implements AuthenticationToken {

    private String token;
    public ShiroAuthToken(String token){
        this.token=token;
    }
    @Override
    public String getPrincipal() {
        return token;
    }

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

登录方法,获取token和用户信息

/**
     * 登录操作
     *
     * @return
     */
    @RequestMapping(value = "/doLogin", method = {RequestMethod.GET, RequestMethod.POST})
    public RestResponse<UserInfo> doLogin(@RequestBody LoginData entity) {
        RestResponse<UserInfo> restResponse = new RestResponse<>();
        LoginData loginData = entity;

        String username = loginData.getUsername();
        String password = loginData.getPassword();
        Boolean rememberMe = loginData.getRememberMe();


        User loginUser = userService.findUserByName(username);
        if (loginUser == null) {
            restResponse.setStatus(CommonConstants.USER_NOT_EXIST);
            return restResponse;
        }
        //盐
        String salt = loginUser.getSalt();
        String dbPassword = loginUser.getPassword();
        String encryptedText = DigestUtil.md5Hex(password + salt);
        if (dbPassword.equals(encryptedText)) {

            Map<String, Object> userInfoClaims = new HashMap<>();
            userInfoClaims.put("id", loginUser.getId().toString());
            userInfoClaims.put("username", loginUser.getUsername());
            userInfoClaims.put("role", "user");
            userInfoClaims.put("avatarUrl", loginUser.getAvatarUrl());
            String token = jwtOperator.generateToken(userInfoClaims);


            //String token = JWTUtil.sign(username, password, loginUser.getUuid(), rememberMe);
            UserInfo userInfo = new UserInfo();

            userInfo.setToken(token);
            userInfo.setUser(loginUser);
            restResponse.setResult(userInfo);

        } else {
            restResponse.setStatus(CommonConstants.USER_LOGIN_FILED);
            return restResponse;
        }

        return restResponse;

    }

查询方法,查询用户列表

//controller层
    /**
     * 查询人员列表
     *
     *
     * @return
     * @throws NoSuchFieldException
     */
    @RequestMapping(value = "/getUserList", method = {RequestMethod.POST, RequestMethod.GET})
    public @ResponseBody
    RestResponse<PageInfo<UserVo>> getUserList(@RequestBody BasePageRequest<UserQueryVo> entity) {
        return service.getUserList(entity);
    }

//service层
    public RestResponse<PageInfo<UserVo>> getUserList(BasePageRequest<UserQueryVo> entity) {
        RestResponse<PageInfo<UserVo>> result = new RestResponse<>();
        UserQueryVo queryVo = entity.getEntity();
        PageHelper.startPage(entity.getPageNum(), entity.getPageSize());
        List<UserVo> userVoList = userMapper.getUserList(queryVo);
        result.setResult(new PageInfo<>(userVoList));
        return result;
    }
//mapper层
    List<UserVo> getUserList(UserQueryVo queryVo);
//mapper xml层
 <select id="getUserList" resultType="com.vvvtimes.demo.vo.UserVo">
      select
      `id`, `username`, `salt`, `password`, `wx_id`, `wx_nickname`, `roles`, `avatar_url`, `create_time`, `update_time`
      from user a
      <where>
        <if test="username != null  and username != '' ">
          and a.username  like concat('%', #{username}, '%')
        </if>
      </where>
    </select>

3.接口验证

验证登录接口

curl --location 'http://localhost:9080/login/doLogin' \
--header 'Content-Type: application/json' \
--data '{
    "username":"admin",
    "password":"admin"
}'

验证查询列表接口

curl --location 'http://localhost:9080/user/getUserList' \
--header 'token: adc799d0341a4c84ab5c77c3504d1328' \
--header 'Content-Type: application/json' \
--data '{
    "username":"user1",
    "password":"pass1"
}'

二、jwt+spring security实现

1.依赖导入

<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.10.7</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2.代码编写

config类

package com.vvvtimes.demo.auth.config;

import com.vvvtimes.demo.auth.TokenAuthenticateFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.sessionManagement()
                // 设置 session 为无状态,因为基于 token 不需要 session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .sessionFixation().none()
                .and()
                .authorizeRequests()
                .antMatchers("/login/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
                .addFilterBefore(new TokenAuthenticateFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/login/**");
    }
}

token过滤器类

package com.vvvtimes.demo.auth;

import cn.hutool.json.JSONUtil;
import com.vvvtimes.demo.common.dto.RestResponse;
import com.vvvtimes.demo.util.JwtOperator;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TokenAuthenticateFilter extends OncePerRequestFilter {

    private JwtOperator jwtOperator;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取用户token
        String token = getToken(request);
        try{
            //jwt解析token
            if(jwtOperator.validateToken(token)){
                Claims claims = jwtOperator.getClaimsFromToken(token);

                log.info("认证token");
                //将 token id username 存入上下文

                request.setAttribute("token",token);
                request.setAttribute("id",claims.get("id"));
                request.setAttribute("username",claims.get("username"));

            }else{
                log.warn("token失效");
                writeFailureResponse(response,request);
                return;
            }

        }catch (NullPointerException exception){
            log.warn("token异常NullPointerException");
            writeFailureResponse(response,request);
            return;

        }

        filterChain.doFilter(request, response);

    }

    private void writeFailureResponse(HttpServletResponse response,HttpServletRequest request) throws IOException {
        response.setContentType("application/json;charset=utf-8");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));

        response.setCharacterEncoding("utf-8");
        response.setHeader("ContentType","application/json;charset=utf-8");
        response.setContentType("application/json;charset=utf-8");
        RestResponse<String> restResponse = new RestResponse<>();
        restResponse.setStatus(401);
        restResponse.setMessage("token校验失败");
        String json = JSONUtil.toJsonStr(restResponse);
        response.getWriter().print(json);
    }

    /**
     * 方法获取用户的token
     * @param request
     * @return token
     */
    public String getToken(HttpServletRequest request){
        //获取请求头
        String token = request.getHeader("token");
        //没有请求头就从参数里拿
        if (!StringUtils.hasText(token)){
            token=request.getParameter("token");
        }
        return token;
    }



}

其他代码 工具类,登录 查列表 跟shrio实现一样,不赘述

3.接口验证

跟shrio一样

三、jwt+aop实现

1.依赖导入

<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.10.7</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

2.代码编写

定义注解

package com.vvvtimes.demo.auth;

public @interface CheckLogin {
}

实现注解

package com.vvvtimes.demo.auth;


import com.vvvtimes.demo.auth.exception.AOPSecurityException;
import com.vvvtimes.demo.util.JwtOperator;
import io.jsonwebtoken.Claims;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
public class CheckLoginAspect {

    @Autowired
    private JwtOperator jwtOperator;

    @Around("@annotation(com.vvvtimes.demo.auth.CheckLogin)")
    public  Object checkLogin(ProceedingJoinPoint point) {
        try {
            // 1.从header里面获取token
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

            ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
            HttpServletRequest request = attributes.getRequest();
            String token = request.getHeader("token");

            // 2.校验token是否合法&是否过期,如果不合法或已过期 直接抛出异常,如果合法放行

            Boolean isValid = jwtOperator.validateToken(token);
            if (!isValid ) {
                throw new AOPSecurityException("token不合法");
            }
            //3如果校验成功,那么将客户信息 设置到 request的attribute里面
            Claims claims = jwtOperator.getClaimsFromToken(token);

            request.setAttribute("id",claims.get("id"));
            request.setAttribute("username",claims.get("username"));
            request.setAttribute("role",claims.get("role"));
            request.setAttribute("avatarUrl",claims.get("avatarUrl"));
            

            return point.proceed();
        } catch (Throwable throwable) {
            throw new AOPSecurityException("token不合法");
        }
    }
}

处理异常

package com.vvvtimes.demo.auth;

import cn.hutool.core.lang.copier.Copier;
import com.vvvtimes.demo.auth.exception.AOPSecurityException;
import com.vvvtimes.demo.common.dto.BaseResponse;
import com.vvvtimes.demo.common.dto.RestResponse;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
@Slf4j
public class GlobalExceptionErrorHanlder {

    @ExceptionHandler(AOPSecurityException.class)
    public ResponseEntity<RestResponse<String>> error(AOPSecurityException e) {
        log.warn("发生AOPSecurityException异常",e);
        RestResponse<String> restResponse = new RestResponse<>();
        restResponse.setStatus(401);
        restResponse.setMessage("token校验失败");
        ResponseEntity<RestResponse<String>> response = new ResponseEntity<RestResponse<String>>(
                restResponse,
                HttpStatus.UNAUTHORIZED
        );
        return response;
    }
}

自定义异常类

package com.vvvtimes.demo.auth.exception;

public class AOPSecurityException extends RuntimeException {
    public AOPSecurityException(String string) {
        super(string);
    }
}

使用时注意在需要token的方法上加上CheckLogin注解

 /**
     * 查询人员列表
     *
     *
     * @return
     * @throws NoSuchFieldException
     */
    @CheckLogin
    @RequestMapping(value = "/getUserList", method = {RequestMethod.POST, RequestMethod.GET})
    public @ResponseBody
    RestResponse<PageInfo<UserVo>> getUserList(@RequestBody BasePageRequest<UserQueryVo> entity) {
        return service.getUserList(entity);
    }

其他工具类 登录 查列表代码与shrio方式一样

3.接口验证

跟shrio方式一样

四、redis token+aop实现

相比较来说,redis只需要参照实现前面的jwt工具类就行了,这里做了个简单的限制,限制单用户只能生成10个不同的token,如果多生成,则注销最早的token

1.加依赖

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.3</version>
        </dependency>

2.写配置

spring:
  redis:
    host: localhost
    port: 6379

这里配置redis密码,生产环境强烈配置密码,见过太多redis入侵了

3.写代码

redis工具类

package com.vvvtimes.demo.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired(required = false)
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        RedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(stringSerializer);
        this.redisTemplate = redisTemplate;
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean rpush(String key, String value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public Object lpop(String key) {
        return redisTemplate.opsForList().leftPop(key);
    }

    public Long llen(String key) {
        return redisTemplate.opsForList().size(key);
    }


    public Long getExpire(String token) {

        return redisTemplate.getExpire(token);

    }

    public Boolean delete(String token) {
        return redisTemplate.delete(token);
    }

    public boolean set(String key, String value, long time) {
        try {
            redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public Object get(String key) {
           return redisTemplate.opsForValue().get(key);
    }


    /*
     * 设置key有效期
     */
    public boolean expire(String key,long time) {
        return redisTemplate.expire(key, time, TimeUnit.SECONDS);
    }


}

redistoken工具类

package com.vvvtimes.demo.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Component
public class RedisTokenOperator {

    /**
     * 有效期,单位秒
     * - 默认2周
     */
    @Value("${redistoken.expire-time-in-second:1209600}")
    private Long expirationTimeInSecond;

    public static final String TOKEN_PREFIX_KEY = "token:";

    public static final String USER_PREFIX_KEY = "user:";

    public static final Integer SINGLE_USER_MAX_TOKEN_SIZE = 10;

    /*@Autowired
    private RedisTemplate redisTemplate;*/

    @Autowired
    private RedisUtil redisUtil;

    /*
     * 从token中获取UserMap
     */
    public Map<Object, Object> getUserMapFromToken(String token) {
        String key = "token:" + token;
        return redisUtil.hmget(key);
    }

    /*
     * 从token中获取过期日
     */
    public Date getExpirationDateFromToken(String token) {
        Long expire = redisUtil.getExpire(token);
        return new Date(System.currentTimeMillis() + expire * 1000);
    }

    /*
     * 判断token是否过期
     */
    private Boolean isTokenExpired(String token) {
        String key = TOKEN_PREFIX_KEY + token;

        Map<Object, Object> hmget = redisUtil.hmget(key);
        if (hmget != null && hmget.size() > 0) {
            return false;
        }
        return true;
    }

    /*
     * 生成token
     */
    public String generateToken(Map<String, Object> map) {
        String userId = (String) map.get("id");
        String username = (String) map.get("username");
        String role = (String) map.get("role");
        String avatarUrl = (String) map.get("avatarUrl");

        //如果之前生成过,则用之前的token
        String userKey = USER_PREFIX_KEY + userId;
        Long oldTokenSize = redisUtil.llen(userKey);

        //多设备登录达到最大值
        if (oldTokenSize != null && oldTokenSize >= SINGLE_USER_MAX_TOKEN_SIZE) {
            while(oldTokenSize >= SINGLE_USER_MAX_TOKEN_SIZE){
                //取出最早的token,注销
                String oldToken = (String) redisUtil.lpop(userKey);

                //注销tokenkey
                String oldTokenKey = TOKEN_PREFIX_KEY + oldToken;
                redisUtil.delete(oldTokenKey);
                oldTokenSize = redisUtil.llen(userKey);
            }
        }

        Map<String, Object> redisMap = new HashMap<>();
        redisMap.put("id", userId);
        redisMap.put("username", username);
        redisMap.put("role", role);
        redisMap.put("avatarUrl", avatarUrl);
        // 生成token并缓存用户信息
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        String key = TOKEN_PREFIX_KEY + token;

        redisUtil.hmset(key, redisMap, expirationTimeInSecond);
        redisUtil.rpush(userKey, token, expirationTimeInSecond);
        return token;
    }

    /*
     * 判断key是否有效
     */
    public Boolean validateToken(String token) {
        return !isTokenExpired(token);
    }


}

定义注解

package com.vvvtimes.demo.auth;

public @interface CheckLogin {
}

实现注解

package com.vvvtimes.demo.auth;


import com.vvvtimes.demo.auth.exception.AOPSecurityException;
import com.vvvtimes.demo.util.RedisTokenOperator;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

@Aspect
@Component
public class CheckLoginAspect {

    @Autowired
    private RedisTokenOperator redisTokenOperator;

    @Around("@annotation(com.vvvtimes.demo.auth.CheckLogin)")
    public  Object checkLogin(ProceedingJoinPoint point) {
        try {
            // 1.从header里面获取token
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

            ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
            HttpServletRequest request = attributes.getRequest();
            String token = request.getHeader("token");

            // 2.校验token是否合法&是否过期,如果不合法或已过期 直接抛出异常,如果合法放行

            Boolean isValid = redisTokenOperator.validateToken(token);
            if (!isValid ) {
                throw new AOPSecurityException("token不合法");
            }
            //3如果校验成功,那么将客户信息 设置到 request的attribute里面
            Map<Object, Object> claims = redisTokenOperator.getUserMapFromToken(token);

            request.setAttribute("id",claims.get("id"));
            request.setAttribute("username",claims.get("username"));
            request.setAttribute("role",claims.get("role"));
            request.setAttribute("avatarUrl",claims.get("avatarUrl"));
            

            return point.proceed();
        } catch (Throwable throwable) {
            throw new AOPSecurityException("token不合法");
        }
    }
}

处理异常

package com.vvvtimes.demo.auth;

import cn.hutool.core.lang.copier.Copier;
import com.vvvtimes.demo.auth.exception.AOPSecurityException;
import com.vvvtimes.demo.common.dto.BaseResponse;
import com.vvvtimes.demo.common.dto.RestResponse;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
@Slf4j
public class GlobalExceptionErrorHanlder {

    @ExceptionHandler(AOPSecurityException.class)
    public ResponseEntity<RestResponse<String>> error(AOPSecurityException e) {
        log.warn("发生AOPSecurityException异常",e);
        RestResponse<String> restResponse = new RestResponse<>();
        restResponse.setStatus(401);
        restResponse.setMessage("token校验失败");
        ResponseEntity<RestResponse<String>> response = new ResponseEntity<RestResponse<String>>(
                restResponse,
                HttpStatus.UNAUTHORIZED
        );
        return response;
    }
}

定义异常

package com.vvvtimes.demo.auth.exception;

public class AOPSecurityException extends RuntimeException {
    public AOPSecurityException(String string) {
        super(string);
    }
}

登录方法没有Claim类了,一并更改

/**
     * 登录操作
     *
     * @return
     */
    @RequestMapping(value = "/doLogin", method = {RequestMethod.GET, RequestMethod.POST})
    public RestResponse<UserInfo> doLogin(@RequestBody LoginData entity) {
        RestResponse<UserInfo> restResponse = new RestResponse<>();
        LoginData loginData = entity;

        String username = loginData.getUsername();
        String password = loginData.getPassword();
        Boolean rememberMe = loginData.getRememberMe();


        User loginUser = userService.findUserByName(username);
        if (loginUser == null) {
            restResponse.setStatus(CommonConstants.USER_NOT_EXIST);
            return restResponse;
        }
        //盐
        String salt = loginUser.getSalt();
        String dbPassword = loginUser.getPassword();
        String encryptedText = DigestUtil.md5Hex(password + salt);
        if (dbPassword.equals(encryptedText)) {

            Map<String, Object> userInfoClaims = new HashMap<>();
            userInfoClaims.put("id", loginUser.getId().toString());
            userInfoClaims.put("username", loginUser.getUsername());
            userInfoClaims.put("role", "user");
            userInfoClaims.put("avatarUrl", loginUser.getAvatarUrl());
            String token = redisTokenOperator.generateToken(userInfoClaims);


            //String token = JWTUtil.sign(username, password, loginUser.getUuid(), rememberMe);
            UserInfo userInfo = new UserInfo();

            userInfo.setToken(token);
            userInfo.setUser(loginUser);
            restResponse.setResult(userInfo);

        } else {
            restResponse.setStatus(CommonConstants.USER_LOGIN_FILED);
            return restResponse;
        }

        return restResponse;

    }

其他代码跟shrio没有区别

4.接口验证

跟shrio一致。

redis里看到的token如下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1169485.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

奇偶校验码和循环冗余码

在数据链路层的传输中&#xff0c;1可能变成0&#xff0c;0可能变成1&#xff0c;这是比特差错。 为了应对比特差错&#xff0c;有两种方式&#xff0c;即自动重传请求ARQ&#xff08;Automatic Repeat-reQuest&#xff09;和前向纠错FEC&#xff08;Forward Error Correction&…

数字电路与逻辑设计 触发器

与非门构成的RS触发器 在这个中禁止RS 00 要记住s对应Q 或非门构成的RS触发器 注意这里的RS换了位置 且不允许RS 11 同步触发器 钟控RS触发器 钟控D触发器 cp 为 0 的时候不变 钟控JK触发器 00不变11改&#xff0c;JK不同随J摆 钟控T触发器 什么是空翻&am…

APLHA开发板系统启动

一. 简介 前面学习了 uboot的移植&#xff0c;在 NXP的 Kernel内核源码里添加 ALPHA开发板的工作。本文为设置CPU主频做准备&#xff0c;开发板从 Nand-Flash中启动根文件系统。 具体来说&#xff0c;我的目的是开发板能正常启动&#xff08;从Nand-Flash加载根文件系统&#…

MyBatis底层原理(小白版本)

&#xff01;特别声明&#xff01;&#xff1a;这篇文章只是单纯用来应对面试&#xff0c;并不能用来当作深度解析的文章来看。本人才疏学浅&#xff0c;文章也可能有不对的地方&#xff0c;望指正。 此源码分析使用的是Java11 基本使用流程&#xff1a; String resource &q…

DAY43 完全背包理论基础 + 518.零钱兑换II

完全背包 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 完全背包和01背包问题唯一不同…

【51单片机】串口与LED点阵屏(学习笔记)

一、串口 1、串口的概述 串口是一种应用十分广泛的通讯接口&#xff0c;串口成本低、容易使用、通信线路简单&#xff0c;可实现两个设备的互相通信。 单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信&#xff0c;极大的扩展了单片机的应用…

数据中心如何散热?

数据中心的散热是一个非常重要的问题&#xff0c;因为数据中心内运行的服务器、存储设备以及网络设备等都会产生大量的热量&#xff0c;如果不能有效地进行散热&#xff0c;将会导致设备故障和性能下降。下面是一些常见的数据中心散热方法&#xff1a; 空调系统&#xff1a;数据…

20231103配置cv180zb的编译环境【填坑篇】

20231103配置cv180zb的编译环境【填坑篇】 2023/11/3 11:36 感谢您选择了晶视科技的cv180zb&#xff0c;让我们一起来填坑。 在你根据文档找不到答案的时候&#xff0c;是不是想把他们家那个写文档的家伙打一顿&#xff0c;我顶你。 当你在在网上找一圈&#xff0c;BAIDU/BING/…

C++初阶(八)类和对象

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、Static成员1、Static概念2、Static特性3、试题 二、友元1、友元的类型2、友元函数3、 友元…

CSS中flex和inline-flex的区别

CSS中flex和inline-flex的区别 起因 display:flex\inline-flex是CSS中弹性布局时&#xff0c;用于容器元素的样式选项。 让人有些糊涂&#xff0c;两者的区别时什么。网上看了一些文章都在扯什么宽度之类的&#xff0c;完全就是胡扯。 还有的写什么“将对象作为弹性伸缩盒显示…

【软件STM32cubeIDE下H73xx配置串口uart1+中断接收/DMA收发+HAL库+简单数据解析-基础样例】

#【软件STM32cubeIDE下H73xx配置串口uart1中断接收/DMA收发HAL库简单数据解析-基础样例】 1、前言2、实验器件3-1、普通收发中断接收实验第一步&#xff1a;代码调试-基本配置&#xff08;1&#xff09;基本配置&#xff08;3&#xff09;时钟配置&#xff08;4&#xff09;保存…

TEMU拼多多跨境平台要求提供的UL测试报告如何办理?电子产品UL测试标准要求

平台销售的电子产品&#xff0c;要符合指定的标准&#xff0c;如果不合格很容易发生起火&#xff0c;等危及消费者生命财产的安全&#xff0c;因此很多客户因为缺少UL报告&#xff0c;导致产品被下架&#xff0c;销售权被移除等问题&#xff0c;也少不了同行之间的恶意举报触发…

ActiveMQ、RabbitMQ、RocketMQ、Kafka介绍

一、消息中间件的使用场景 消息中间件的使用场景总结就是六个字&#xff1a;解耦、异步、削峰 1.解耦 如果我方系统A要与三方B系统进行数据对接&#xff0c;推送系统人员信息&#xff0c;通常我们会使用接口开发来进行。但是如果运维期间B系统进行了调整&#xff0c;或者推送过…

如何开始短视频的制作,短视频脚本如何写?

在短视频创作拍摄的过程中&#xff0c;你有没有遇到过类似的情况&#xff1a; 拍摄拍到中途手忙脚乱的&#xff0c;不知道接下来该拍摄什么类容&#xff0c;或者拍了一半发现拍摄场景不行&#xff0c;又重新调整拍摄场景&#xff0c;再者&#xff0c;拍摄过程中发现缺少了拍摄道…

【面试专题】设计模式篇①

1.工厂设计模式 工厂设计模式是一种创建型模式&#xff0c;它提供了一种创建对象的接口&#xff0c;但具体创建的对象类型可以在运行时决定。工厂设计模式主要解决的是创建对象的灵活性问题。 工厂设计模式主要包括简单工厂模式、工厂方法模式和抽象工厂模式三种。 简单工厂…

虹科示波器 | 汽车免拆检修 | 2010款江铃陆风X8车发动机怠速抖动、加速无力

一、故障现象 一辆2010款江铃陆风X8车&#xff0c;搭载4G6GS4N发动机&#xff0c;累计行驶里程约为20万km。该车在其他修理厂进行发动机大修&#xff0c;维修后试车&#xff0c;发动机怠速抖动、加速无力。用故障检测仪检测&#xff0c;发动机控制模块&#xff08;ECM&#xff…

JVM常用命令

jps —查看pid jstat -gcutil 4364 1000 2000 —查看堆内存占用百分比&#xff0c;每秒打印1次&#xff0c;总共打印2000次 S0&#xff1a;幸存1区当前使用比例 S1&#xff1a;幸存2区当前使用比例 E&#xff1a;伊甸园区使用比例 O&#xff1a;老年代使用比例 M&#xff1a;元…

常用的Linux远程桌面配置方法

TigerVNC 是 VNC&#xff08;虚拟网络计算&#xff09;的高性能、平台中立的实现&#xff0c;VNC 是一种客户端/服务器应用程序&#xff0c;允许用户在远程计算机上启动图形应用程序并与之交互。 TigerVNC 提供运行 3D 和视频应用程序所需的性能水平&#xff0c;并尝试在其支持…

基于单片机的温室环境数据监测系统的设计

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、总体方案设计2.1 总体架构设计 二、整体硬件电路设计3.1 主控制器电路 三 系统设计概要4.2 主程序设计原理图程序 四、 结论五、 文章目录 概要 与农业发达国家相比&#xff0c;我国的农业科技方面还处于刚刚…