手把手教你前后分离架构系统认证鉴权实现(代码片段)

dehuisun dehuisun     2023-04-07     539

关键词:

前面我们实现了前后分离项目基础的数据交互以及前端数据展示。用户登录部分一直是模拟登录,今天我们实现系统的身份认证部分让系统不在裸奔。

1、系统认证授权

认证就是要核验用户的身份,比如说通过用户名和密码来检验用户的身份。说简单一些,认证就是登陆。登陆之后Shiro要记录用户成功登陆的凭证。
授权是比认证更加精细度的划分用户的行为。比如说一个教务管理系统中,学生登陆之后只能查看信息,不能修改信息。而班主任就可以修改学生的信息。这就是利用授权来限定不同身份用户的行为。
安全是应用中不可缺少的功能,相较于其他认证与授权框架,Shiro设计的非常简单,所以广受好评。任意JavaWeb项目都可以使用Shiro框架。

我们采用shiro + JWT架构来实现系统安全认证。只使用jwt只能实现基础验证功能,所以我们把token存储再redis中,利用redis我们可以实现token踢出、token刷新等功能。

Shiro可以利用HttpSession或者Redis存储用户的登陆凭证,以及角色或者身份信息。然后利用过滤器(Filter),对每个Http请求过滤,检查请求对应的HttpSession或者Redis中的认证与授权信息。如果用户没有登陆,或者权限不够,那么Shiro会向客户端返回错误信息。

JWT(Json Web Token), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。JWT一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

如果用户的登陆凭证经过加密(Token)保存在客户端,客户端每次提交请求的时候,把Token上传给后端服务器节点。即便后端项目使用了负载均衡,每个后端节点接收到客户端上传的Token之后,经过检测,是有效的Token,于是就断定用户已经成功登陆,接下来就可以提供后端服务了。

传统的HttpSession依靠浏览器的Cookie存放SessionId,所以要求客户端必须是浏览器。现在的JavaWeb系统,客户端可以是浏览器、APP、小程序,以及物联网设备。为了让这些设备都能访问到JavaWeb项目,就必须要引入JWT技术。JWT的Token是纯字符串,至于客户端怎么保存,没有具体要求。只要客户端发起请求的时候,附带上Token即可。所以像物联网设备,我们可以用SQLite存储Token数据。

认证流程

​​​​​​​

2、SpringBoot集成redis

redis是一个key-value

2.1、​​​​​​​添加pom依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

 2.2 添加启动类

@Configuration
public class RedisConfig 
 
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) 
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        // key采用String的序列化方式
        template.setKeySerializer(new StringRedisSerializer());
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        // value序列化方式采用jackson
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    

2.3、配置文件

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.timeout=1000
spring.redis.database=0

3、SpringBoot集成shiro

3.1、添加依赖

<shiro.version>1.2.4</shiro.version>

<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>$shiro.version</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>$shiro.version</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>$shiro.version</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>$shiro.version</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8</version>
        </dependency>
 
         <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>

3.2、添加启动类

@Configuration
public class ShiroConfig 
 
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) 
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //拦截器
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/druid/**", "anon");
        filterChainDefinitionMap.put("/swagger**/**", "anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");
        filterChainDefinitionMap.put("/v2/**", "anon");
        filterChainDefinitionMap.put("/doc.html", "anon");
        filterChainDefinitionMap.put("/document.html", "anon");
        filterChainDefinitionMap.put("/configuration/ui", "anon");
        filterChainDefinitionMap.put("/swagger-resources", "anon");
        filterChainDefinitionMap.put("/authentication/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/actuator/**", "anon");
        filterChainDefinitionMap.put("/sys/login/login", "anon");
 
        Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
        filterMap.put("authc", new JwtFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    
 
    @Bean("securityManager")
    public SecurityManager securityManager(MyRealm myRealm) 
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
 
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
 
        return securityManager;
    

3.3、shiro实现

3.3.1、MyRealm实现类

@Component
public class MyRealm extends AuthorizingRealm 
 
    @Autowired
    private ISysUserService sysUserService;
 
    @Autowired
    private RedisTemplate redisTemplate;
 
    /**
     * 必须重写此方法,不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) 
        return token instanceof JwtToken;
    
 
    /**
     * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) 
        return new SimpleAuthorizationInfo();
    
 
    /**
     * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException 
        String token = (String) auth.getCredentials();
 
        if (token == null) 
            throw new AuthenticationException("token为空!");
        
        // 校验token有效性
        Object user = this.checkToken(token);
        return new SimpleAuthenticationInfo(user, token, getName());
    
 
    /**
     * 校验token的有效性
     */
    public Object checkToken(String token) throws AuthenticationException 
        // 解密获得username,用于和数据库进行对比
        String userId = JwtUtil.getUserId(token);
        if (StringUtils.isBlank(userId)) 
            throw new AuthenticationException("token无效");
        
 
        SysUser sysUser = sysUserService.getById(userId);
        if (sysUser == null) 
            throw new AuthenticationException("用户不存在!");
        
       //  判断用户状态
        if (Constant.no.equals(sysUser.getStatus())) 
            throw new AuthenticationException("账号已被锁定,请联系管理员!");
        
        // 校验token是否超时失效 & 或者账号密码是否错误
        if (!jwtTokenRefresh(token, String.valueOf(sysUser.getId()))) 
            throw new AuthenticationException("Token失效,请重新登录!");
        
        return sysUser;
 
    
 
    /**
     * 刷新token
     */
    public boolean jwtTokenRefresh(String token, String userId) 
        String cacheToken = (String) redisTemplate.opsForValue().get(Constant.CATCHE_TOKEN + token);
        if (StringUtils.isNotEmpty(cacheToken)) 
            // 校验token有效性
            if (!JwtUtil.verify(cacheToken)) 
                String newToken = JwtUtil.sign(userId);
                // 设置超时时间
                redisTemplate.opsForValue().set(Constant.CATCHE_TOKEN + token, newToken, JwtUtil.expire * 2, TimeUnit.SECONDS);
            
            return true;
        
        return false;
    
 

3.3.2、过滤器实现

public class JwtFilter extends BasicHttpAuthenticationFilter 
    /**
     * 执行登录认证
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) 
    	 if(((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name()))
             return true;
         
         return super.isAccessAllowed(request, response, mappedValue);
    
 
    /**
     *认证失败回调方法
     */
    @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");
 
        try 
            //处理登录失败的异常
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            String json = JSONObject.toJSONString(Result.noAccess().info(throwable.getMessage()));
            httpResponse.getWriter().print(json);
         catch (Exception exp) 
        	exp.printStackTrace();
        
 
        return false;
    
 
	@Override
	protected AuthenticationToken createToken(ServletRequest request,
			ServletResponse response)  
		  //获取请求token
        String token = getRequestToken((HttpServletRequest) request);
        if(StringUtils.isBlank(token))
            return null;
        
        return new JwtToken(token);
	
 
    /**
     * 处理未经身份验证的请求
     */
	@Override
	protected boolean onAccessDenied(ServletRequest request,
			ServletResponse response) throws Exception 
		 //获取请求token,如果token不存在,直接返回401
        String token = getRequestToken((HttpServletRequest) request);
        if(StringUtils.isBlank(token))
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setContentType("application/json;charset=utf-8");
            String json = JSONObject.toJSONString(Result.noAccess());
            httpResponse.getWriter().print(json);
            return false;
        
        return executeLogin(request, response);
	
 
	 /**
     * 获取请求中的token
     */
    private String getRequestToken(HttpServletRequest httpRequest)
        //从header中获取token
        String token = httpRequest.getHeader(Constant.token);
        //如果header中不存在token,则从参数中获取token
        if(StringUtils.isBlank(token)||("null").equals(token))
            token = httpRequest.getParameter(Constant.token);
        
        //如果header中不存在token,则从参数中获取token
        if(StringUtils.isBlank(token)||("null").equals(token))
            Cookie[] cookies =  httpRequest.getCookies();
            if(cookies != null)
                for(Cookie cookie : cookies)
                    if(cookie.getName().equals(Constant.token))
                        token =  cookie.getValue();
                    
                
            
        
        return token;
    

3.3.3、JwtUtil工具类 

@Slf4j
@ConfigurationProperties(prefix = "mir.jwt")
@Component
@Data
public class JwtUtil 
 
    private static String secret;
    public static long expire;
 
    /**
     * 生成jwt token
     */
    public static String sign(String userId) 
        //过期时间
        Date expireDate = new Date(System.currentTimeMillis() + expire * 1000);
 
        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(userId)
                .setIssuedAt(new Date())
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    
 
    /**
     * 校验token
     */
    public static boolean verify(String token) 
        try 
            Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
            return true;
        catch (Exception e)
            return false;
        
    
 
    /**
     * 获取userId
     */
    public static String getUserId(String token) 
        try 
            Claims claims=Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
            return claims.getSubject();
        catch (ExpiredJwtException e)
            Claims claims = e.getClaims();
            return claims.getSubject();
        catch (Exception e)
            return null;
        
    
 
    public String getSecret() 
        return secret;
    
 
    public void setSecret(String secret) 
        JwtUtil.secret = secret;
    
 
    public long getExpire() 
        return expire;
    
 
    public void setExpire(long expire) 
        JwtUtil.expire = expire;
    

3.3.4、JwtToken实体

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;
    

3.3.5、自定义配置文件

mir.jwt.secret = 123456
mir.jwt.expire = 900

4、登录功能实现

4.1、后端登录接口实现

@ApiOperation(value = "账号密码方式登录")
@PostMapping("/login")
public Result login(@Valid LoginParam loginParam)throws Exception 
   try 
      User user=new User();
      user.setAccount(loginParam.getAccount());
      List<User> list = userService.list(new QueryWrapper<>(user));
      if(list!=null&&list.size()>0)
         user=list.get(0);
         if(Constant.LOCK.equals(user.getStatus()))
            if(user.getLockTime()!=null) 
               int min = (int) (System.currentTimeMillis() - user.getLockTime().getTime())/(1000 * 60);
               if(min>=lock_time)
                  user.setLockTime(null);
                  user.setStatus(Constant.YES);
                  user.setErrNum(0);
                  userService.updateById(user);
               
            
         

         if(Constant.YES.equals(user.getStatus()))
            if(loginParam.getPassword().equals(user.getPassword()))
               if(user.getErrNum()!=null&&user.getErrNum()>0)
                  user.setErrNum(0);
                  user.setLockTime(null);
                  userService.updateById(user);
               
               String token= JwtUtil.sign(String.valueOf(user.getId()));
               redisTemplate.opsForValue().set(Constant.CATCHE_TOKEN + token,token,JwtUtil.expire * 2, TimeUnit.SECONDS);

               HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
               Cookie cookie = new Cookie("token", token);
               cookie.setMaxAge(604800);
               cookie.setHttpOnly(true);
               cookie.setPath("/");
               response.addCookie(cookie);
               return Result.ok().info(token);
            else
               Integer errNum=0;

               if(user.getErrNum()!=null)
                  errNum=user.getErrNum()+1;
               else
                  errNum=errNum+1;
               
               if(errNum>=max_err_num)
                  user.setLockTime(new Date());
                  user.setStatus(Constant.LOCK);
               else
                  user.setErrNum(errNum);
               
               userService.updateById(user);

               return Result.error().message("用户密码错误,再错误"+(max_err_num-errNum)+"次,用户将锁定");
            
         else if(Constant.LOCK.equals(user.getStatus()))
            return Result.error().message("账号已锁定");
         else 
            return Result.error().message("账号已停用");
         
      else
         return Result.error().message("账号或密码错误");
      
    catch (Exception e)
      log.error("登录异常", e);
      return Result.error().message("登录失败");
   

 4.2、前端统一请求

4.2.1、请求方法封装

创建http工具类

import Vue from 'vue'
import axios from 'axios'
import router from '@/router'
import qs from 'qs'
import merge from 'lodash/merge'
import  clearLoginInfo  from '@/utils'
 
const http = axios.create(
  timeout: 1000 * 3,
  withCredentials: true,
  headers: 
    'Content-Type': 'application/json; charset=utf-8'
  
)
 
/**
 * 请求拦截
 */
http.interceptors.request.use(config => 
  config.headers['token'] = Vue.cookie.get('token') // 请求头带上token
  return config
, error => 
  return Promise.reject(error)
)
 
/**
 *
 * 响应拦截
 */
http.interceptors.response.use(response => 
  if (response.data.code === 401)  // 401, token失效
    clearLoginInfo()
    router.push( name: 'login' ,params:  data: response.data.messages )
  
  return response
, error => 
  return Promise.reject(error)
)
 
/**
 * 请求地址处理
 * @param * actionName action方法名称
 */
http.adornUrl = (actionName) => 
  // 非生产环境 && 开启代理, 接口前缀统一使用[/proxyApi/]前缀做代理拦截!
  return (process.env.OPEN_PROXY  ? '/proxyApi' : window.SITE_CONFIG.baseUrl) + actionName

/**
 * 请求方法处理
 * @param methodName
 * @returns *
 */
http.adornMethod = (methodName) =>
  return methodName;

/**
 * get请求参数处理
 * @param * params 参数对象
 * @param * openDefultParams 是否开启默认参数?
 */
http.adornParams = (params = , openDefultParams = true) => 
  var defaults = 
    't': new Date().getTime()
  
  return openDefultParams ? merge(defaults, params) : params

 
/**
 * post请求数据处理
 * @param * data 数据对象
 * @param * openDefultdata 是否开启默认数据?
 * @param * contentType 数据格式
 *  json: 'application/json; charset=utf-8'
 *  form: 'application/x-www-form-urlencoded; charset=utf-8'
 */
http.adornData = (data = , openDefultdata = true, contentType = 'json') => 
  var defaults = 
    't': new Date().getTime()
  
  data = openDefultdata ? merge(defaults, data) : data
  return contentType === 'json' ? JSON.stringify(data) : qs.stringify(data)

 
export default http

4.2.2、请求地址代理方式 

config/index.js

const devEnv = require('./dev.env')

 proxyTable: devEnv.OPEN_PROXY === false ?  : 
      '/proxyApi': 
        target: 'http://127.0.0.1:8888/',
        changeOrigin: true,
        pathRewrite: 
          '^/proxyApi': '/'
        
      
,

4.2.3、请求​​​​​​​地址常量方式 

static/config/index.js

;(function () 
  window.SITE_CONFIG = ;
  // api接口请求地址
  window.SITE_CONFIG['baseUrl'] = 'http://localhost:8888';
  // cdn地址 = 域名 + 版本号
  window.SITE_CONFIG['domain']  = './'; // 域名
  window.SITE_CONFIG['version'] = '';   // 版本号(年月日时分)
  window.SITE_CONFIG['cdnUrl']  = window.SITE_CONFIG.domain + window.SITE_CONFIG.version;
)();

Index.html引入index.js 

<script src="./static/config/index.js"></script>

4.3、​​​​​​​登录页面实现

使用统一请求工具类

this.$http(
  url: this.$http.adornUrl("/sys/login"),
  method: 'post',
  params: this.$http.adornParams(this.dataForm)
).then((data) => 
  if (data.success) 
    this.$router.replace( name: 'home' )
  else
    this.dataForm.password="";
    this.$message.error(data.message)
    this.loading = false;
  
).catch(data => 
  this.loading = false;
  this.dataForm.password="";
  this.$message.error(this.tips.error);
)

 4.4密码加密传输

当前密码为明文传输,很不安全,所以需要加密传输

4.4.1、前端加密

npm install jsencrypt –d

工具类

import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'

const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\\n' +
  'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
 
// 加密
export function encrypt(txt) 
  const encryptor = new JSEncrypt()
  encryptor.setPublicKey(publicKey) // 设置公钥
  return encryptor.encrypt(txt) // 对数据进行加密

登录页面

import  encrypt  from '@/utils/jsencrypt'
this.dataForm.password=encrypt(this.dataForm.password)

4.4.2后端解密 

<dependency>
     <groupId>commons-codec</groupId>
     <artifactId>commons-codec</artifactId>
     <version>1.10</version>
</dependency>

 添加工具类

package com.sq.auth.utils;
 
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
@Slf4j
public class RSAEncrypt 
    
    // 密钥对生成 http://web.chacuo.net/netrsakeypair
 
    private static String publicKey = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\\n" +
            "nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==";
 
    private static String privateKey = "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\\n" +
            "7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\\n" +
            "PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\\n" +
            "kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\\n" +
            "cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\\n" +
            "DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\\n" +
            "YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\\n" +
            "UP8iWi1Qw0Y=";
    /**
     * RSA公钥加密
     * @param str 加密字符串
     * @param publicKey 公钥
     * @return 密文
     * @throws Exception 加密过程中的异常信息
     */
    public static String encrypt( String str, String publicKey ) throws Exception
        //base64编码的公钥
        byte[] decoded = Base64.decodeBase64(publicKey);
        RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
        //RSA加密
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes("UTF-8")));
        return outStr;
    
 
    /**
     * RSA私钥解密
     * @param str 加密字符串
     * @return 铭文
     * @throws Exception 解密过程中的异常信息
     */
    public static String decrypt(String str) throws Exception
        String outStr="";
        try 
            //64位解码加密后的字符串
            byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));
            //base64编码的私钥
            byte[] decoded = Base64.decodeBase64(privateKey);
            RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
            //RSA解密
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, priKey);
             outStr = new String(cipher.doFinal(inputByte));
        catch (Exception e)
            log.error("RSA解密失败",e);
        finally 
            return outStr;
        
    

4.5、登出实现

this.$http(
  url: this.$http.adornUrl('/sys/logout'),
  method: 'post',
  data: this.$http.adornData()
).then((data) => 
  if (data && data.success === true) 
    this.$router.push( name: 'login' )
  
).catch(() => 
  this.$message.error(this.tips.error);
)

 

关注公众号”小猿架构“,发送 "前后分离架构" ,下载课程视频+课程源码+课件。

手把手教你通过docker部署前后端分离项目(亲测可用)(代码片段)

点击关注公众号,实用技术文章及时了解安装Docker安装Nginx安装Mysql部署SpringBoot项目部署Vue项目一、安装Docker1、安装:yum install docker2、启动/停止/重启docker服务service docker start service docker stop service docker restart3、查... 查看详情

手把手教你搭建springboot+vue前后端分离(代码片段)

1什么是前后端分离前后端分离是目前互联网开发中比较广泛使用的开发模式,主要是将前端和后端的项目业务进行分离,可以做到更好的解耦合,前后端之间的交互通过xml或json的方式,前端主要做用户界面的渲... 查看详情

手把手教你用java实现一套简单的鉴权服务(springboot,ssm)(万字长文)(代码片段)

...、利用servlet+jdbc实现简单的用户登录程序1.明确思路2.手把手教你实现一个简单的web登录程序①创建web项目②编写简单的登录页面③编写servlet程序④封装jdbc操作,编写简单的数据库连接池⑤操作数据库⑥配置web.xml⑦idea运... 查看详情

手把手教你shiro整合jwt实现登录认证(代码片段)

1.所用技术SpringBootMybatis-plusShiroJWTRedis2.前置知识Shiro:Shiro是一个基于Java的开源的安全框架。在Shiro的核心架构里面,Subject是访问系统的用户。SecurityManager是安全管理器,负责用户的认证和授权,相当于Shiro的老大... 查看详情

springboot+springsecurity+jwt实现前后端分离登录认证及权限控制(代码片段)

借鉴文章:Springboot+SpringSecurity实现前后端分离登录认证及权限控制_I_am_Rick_Hu的博客-CSDN博客_springsecurity前后端分离登录认证最近一段时间,公司给我安排了一个公司子系统的后台管理系统,让我实现权限管理。此... 查看详情

手把手教你启动若依前后端分离项目(代码片段)

目录一、概述 二、运行阶段前准备三、运行步骤流程(1)拉取项目(2)将项目导入到IDEA中(3)修改数据库密码(4)启动Redis服务器(5)创建数据库,并执行sql脚本(6)后端... 查看详情

前后端分离项目中springboot集成shiro实现权限控制

文章目录​​使用注解控制鉴权授权​​​​使用url配置控制鉴权授权​​​​表结构​​​​jar包依赖​​​​代码说明​​​​身份认证​​​​权限认证​​​​跨域问题解决​​​​登录验证不进行重定向改为设置http... 查看详情

免费开源的智能家居系统,springboot+vue前后端分离,wifi智能设备接入,手把手开发安卓app,建立qq智能管家机器人!(代码片段)

杂货店牌智能家居系统实操手册手册大纲第一章前后端分离的Web项目1.1前端开发前端开发概述技术分析资料准备环境搭建实现基本页面搭建配套教学视频链接1.2后端开发后端概述技术分析资料准备环境搭建实现基本功能框架配套... 查看详情

3.爱收藏——系统架构

... 爱收藏系统,以微服务为核心,按照业务来划分模块,前后端分离。存储以关系型数据库为主,redis存储登录相关数据。前端使用vue开发,nginx作为静态文件服务器。使用docker部署,容器包括前后端服务、基础服务、初始化服... 查看详情

企业管理系统前后端分离架构设计系列一权限模型篇(代码片段)

原文:企业管理系统前后端分离架构设计系列一权限模型篇前段时间分别用vue和react写了两个后台管理系统的模板vue-quasar-admin和3YAdmin。两个项目中都实现了基于RBAC的权限控制。因为本职工作是后端开发,比较清楚权限控制一个管... 查看详情

springboot2.x开发案例之前后端分离鉴权

前言阅读本文需要一定的前后端开发基础,前后端分离已成为互联网项目开发的业界标准使用方式,通过Nginx代理+Tomcat的方式有效的进行解耦,并且前后端分离会为以后的大型分布式架构、弹性计算架构、微服务架构、多端化服... 查看详情

spingsecurity前后端分离两种方案

前言本篇文章是基于SpringSecurity实现前后端分离登录认证及权限控制的实战,主要包括以下四方面内容:SpringSeciruty简单介绍;通过SpringSeciruty实现的基于表单和Token认证的两种认证方式;自定义实现RBAC的权限控制;跨域问题处理;S... 查看详情

企业管理系统前后端分离架构设计系列一权限模型篇(代码片段)

...心功能,而且是可以做到通用的。打算写写关于管理系统前后端分离方面的文章,也是做一个知识的总结,其中会涉及到vue,react,node,.netcore等方面的知识。术语描述用户(S 查看详情

手把手教你用redis实现一个简单的mq消息队列(java)(代码片段)

众所周知,消息队列是应用系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。目前使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ.但是如果... 查看详情

手把手教你使用labviewopencvdnn实现图像分类(含源码)(代码片段)

(文章目录)前言上一篇和大家一起分享了如何使用LabVIEWOpenCVdnn实现手写数字识别,今天我们一起来看一下如何使用LabVIEWOpenCVdnn实现图像分类。一、什么是图像分类?1、图像分类的概念图像分类,核心是从给定的分类集合中给图... 查看详情

sa-token实现分布式登录鉴权(redis集成前后端分离)

...集合​​​​3.2权限校验​​​​3.3角色校验​​​​4.前后台分离(无Cookie模式)​​​​5.Sa-Token集成Redis​​​​6.SpringBoot集成Sa-Token​​​​6.1创建项目​​​​6.2添加依赖​​​​6.3设置配置文件​​​​6.4创建启动类... 查看详情

手把手教你实现一个抽奖系统(java版)(代码片段)

点击关注公众号,实用技术文章及时了解来源:blog.csdn.net/wang258533488/article/details/789013031概述项目开发中经常会有抽奖这样的营销活动的需求,例如:积分大转盘、刮刮乐、老虎机等等多种形式,其实后台的实... 查看详情

前后端分离之jwt用户认证(代码片段)

  在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了。于是我们的程序就不知道谁是... 查看详情