欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

springsecurity?登錄認證流程分析一(ajax)

 更新時間:2024年08月20日 15:33:12   作者:星空尋流年  
這篇文章主要介紹了springsecurity?登錄認證一(ajax篇),本文通過實例代碼圖文相結(jié)合給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧

一、準備工作

1.1 導(dǎo)入依賴

  因springboot 3.0 + 以上版本只能支持java17 顧使用2.5.0 版本 

  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
       <!-- <version>2.7.18</version>-->
    </parent>
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!-- thymeleaf 相關(guān)依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.11</version>
        </dependency>
        <!-- mybatis坐標 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
           <!-- <version>8.0.28</version>-->
        </dependency>
        <!--validation依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <!--redis坐標-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--springdoc-openapi-->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>2.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
            <version>2.1.0</version>
        </dependency>
        <!--fastjson依賴-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.33</version>
        </dependency>
        <!--jwt依賴-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
    </dependencies>
 <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

二、認證        

2.1 登錄認證流程

接口解釋

        Authentication接口: 它的實現(xiàn)類,表示當(dāng)前訪問系統(tǒng)的用戶,封裝了用戶相關(guān)信息;

        AuthenticationManager接口:定義了認證Authentication的方法;

        UserDetailsService接口:加載用戶特定數(shù)據(jù)的核心接口。里面定義了一個根據(jù)用戶名查詢用戶信息的 方法;

      UserDetails接口:提供核心用戶信息。通過UserDetailsService根據(jù)用戶名獲取處理的用戶信息要封裝 成UserDetails對象返回。然后將這些信息封裝到Authentication對象中;  

2.3  自定義數(shù)據(jù)源分析

①自定義登錄接口 調(diào)用ProviderManager的方法進行認證 如果認證通過生成jwt 把用戶信息存入redis中;

②自定義UserDetailsService 在這個實現(xiàn)類中去查詢數(shù)據(jù)庫;

2.4  自定義數(shù)據(jù)源查詢代碼實現(xiàn)(可實現(xiàn)多數(shù)據(jù)源模式,db2,mysql)  

2.4.1 自定義數(shù)據(jù)源掃描mapper

package com.fashion.config.datasource;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
/**
 * @Author: LQ
 * @Date 2024/8/17 14:23
 * @Description: mysql 配置
 */
@Configuration
@MapperScan(basePackages = "com.fashion.mapper.mysql",sqlSessionFactoryRef = "mysqlSqlSessionFactory")
public class MysqlDataSourceConfig {
    @Primary
    @Bean
    public DataSource mysqlDataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/lq");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }
    @Primary
    @Bean
    public SqlSessionFactory mysqlSqlSessionFactory(@Autowired DataSource mysqlDataSource){
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(mysqlDataSource);
        sessionFactory.setConfigLocation(new ClassPathResource("/mybatis/mybatis-config.xml"));
        try {
            // mapper xml 文件位置
            sessionFactory.setMapperLocations(
                    new PathMatchingResourcePatternResolver()
                            .getResources("classpath:mybatis/mapper/mysql/*.xml"));
          //  sessionFactory.setMapperLocations(new ClassPathResource("/mybatis/mapper/mysql/*.xml"));
            return sessionFactory.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

2.4.2 自定義 UserDetailsService

package com.fashion.service;
import com.fashion.domain.LoginSessionUserInf;
import com.fashion.domain.mysql.TUserInf;
import com.fashion.exception.CustomerAuthenticationException;
import com.fashion.mapper.mysql.TUserInfMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import java.util.Arrays;
import java.util.List;
/**
 * @Author: LQ
 * @Date 2024/8/13 21:12
 * @Description:
 */
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private TUserInfMapper userInfMapper;
    @Override
    public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException {
        // 根據(jù)用戶名獲取用戶信息
        if (ObjectUtils.isEmpty(loginId)) {
            throw new CustomerAuthenticationException("用戶名不能為空!");
        }
        TUserInf tUserInf = userInfMapper.selectByLoginId(loginId);
        if (ObjectUtils.isEmpty(tUserInf)) {
            throw new CustomerAuthenticationException("用戶不存在!");
        }
        // 獲取權(quán)限信息 todo:后期從數(shù)據(jù)庫查詢
        List<String> perList = Arrays.asList("new:query", "news:delete");
        LoginSessionUserInf loginSessionUserInf = new LoginSessionUserInf(tUserInf, perList);
        return loginSessionUserInf;
    }
}

2.4.3 自定義 UserDetails

package com.fashion.domain;
import com.alibaba.fastjson.annotation.JSONField;
import com.fashion.domain.mysql.TUserInf;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
 * @Author: LQ
 * @Date 2024/8/17 15:57
 * @Description: 用戶登錄信息
 */
@Data
public class LoginSessionUserInf implements UserDetails {
    private TUserInf userInf;
    public LoginSessionUserInf() {
    }
    @JsonIgnore
    @JSONField(serialize=false)
    private List<GrantedAuthority> grantedAuthorities;
    // 權(quán)限列表
    private List<String> perList;
    public LoginSessionUserInf(TUserInf userInf, List<String> perList) {
        this.userInf = userInf;
        this.perList = perList;
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if (grantedAuthorities != null) {
            return grantedAuthorities;
        }
        grantedAuthorities = perList.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        return grantedAuthorities;
    }
    @Override
    public String getPassword() {
        return userInf.getLoginPwd();
    }
    @Override
    public String getUsername() {
        return userInf.getLoginId();
    }
    //判斷賬號是否未過期
    @Override
    public boolean isAccountNonExpired() {
        return "1".equals(userInf.getStatus());
    }
    //判斷賬號是否沒有鎖定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    //判斷賬號是否沒有超時
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    //判斷賬號是否可用
    @Override
    public boolean isEnabled() {
        return true;
    }
}

2.4.4 創(chuàng)建用戶sql 

create table t_user_inf(
    id int primary key auto_increment comment '主鍵id',
    login_id varchar(64) default '' comment '登錄賬號id',
    login_pwd varchar(128) default '' comment '登錄密碼',
    user_nm varchar(126) default '' comment '登錄賬號名稱',
    status varchar(2) default '1' comment '狀態(tài) 1正常',
    phone varchar(11) default '' comment '手機號',
    source_type varchar(2) default '1' comment '登錄來源 1 賬密  2 githup',
    address varchar(128) default '' comment '家庭住址',
    cre_date datetime default now() comment '創(chuàng)建時間',
    upd_date datetime default now() comment '更新時間',
    upd_usr varchar(64) default '' comment '更新人'
);

2.4.5  其他實體類(用戶類)

package com.fashion.domain.mysql;
import java.util.Date;
import lombok.Data;
@Data
public class TUserInf {
    /**
    * 主鍵id
    */
    private Integer id;
    /**
    * 登錄賬號id
    */
    private String loginId;
    /**
    * 登錄密碼
    */
    private String loginPwd;
    /**
    * 登錄賬號名稱
    */
    private String userNm;
    /**
    * 狀態(tài) 1正常
    */
    private String status;
    /**
    * 手機號
    */
    private String phone;
    /**
    * 登錄來源 1 賬密  2 githup
    */
    private String sourceType;
    /**
    * 家庭住址
    */
    private String address;
    /**
    * 創(chuàng)建時間
    */
    private Date creDate;
    /**
    * 更新時間
    */
    private Date updDate;
    /**
    * 更新人
    */
    private String updUsr;
}

2.4.6 通用返回類

package com.fashion.domain;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
 * @Author: LQ
 * @Date 2024/8/17 15:08
 * @Description:
 */
@Data
public class R {
    private Boolean success; //返回的成功或者失敗的標識符
    private Integer code; //返回的狀態(tài)碼
    private String message; //提示信息
    private Map<String, Object> data = new HashMap<String, Object>(); //數(shù)據(jù)
    //把構(gòu)造方法私有
    private R() {}
    //成功的靜態(tài)方法
    public static R ok(){
        R r=new R();
        r.setSuccess(true);
        r.setCode(ResultCode.SUCCESS);
        r.setMessage("成功");
        return r;
    }
    //失敗的靜態(tài)方法
    public static R error(){
        R r=new R();
        r.setSuccess(false);
        r.setCode(ResultCode.ERROR);
        r.setMessage("失敗");
        return r;
    }
    //使用下面四個方法,方面以后使用鏈式編程
// R.ok().success(true)
// r.message("ok).data("item",list)
    public R success(Boolean success){
        this.setSuccess(success);
        return this; //當(dāng)前對象 R.success(true).message("操作成功").code().data()
    }
    public R message(String message){
        this.setMessage(message);
        return this;
    }
    public R code(Integer code){
        this.setCode(code);
        return this;
    }
    public R data(String key, Object value){
        this.data.put(key, value);
        return this;
    }
    public R data(Map<String, Object> map){
        this.setData(map);
        return this;
    }
}

2.5  配置類/工具類

package com.fashion.utils;
import cn.hutool.core.util.IdUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
/**
 * @Author: LQ
 * @Date 2024/8/17 15:38
 * @Description: jwt 工具類
 */
public class JwtUtil {
    //有效期為
    public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一個小時
    //設(shè)置秘鑰明文(鹽)
    public static final String JWT_KEY = "LQlacd";
    //生成令牌
    public static String getUUID(){
        String token = IdUtil.fastSimpleUUID();
        return token;
    }
    /**
     * 生成jtw
     * @param subject token中要存放的數(shù)據(jù)(json格式) 用戶數(shù)據(jù)
     * @param ttlMillis token超時時間
     * @return
     */
    public static String createJWT(String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 設(shè)置
        //過期時間
        return builder.compact();
    }
    //生成jwt的業(yè)務(wù)邏輯代碼
    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis,
                                            String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();//獲取到系統(tǒng)當(dāng)前的時間戳
        Date now = new Date(nowMillis);
        if(ttlMillis==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid) //唯一的ID
                .setSubject(subject) // 主題 可以是JSON數(shù)據(jù)
                .setIssuer("xx") // 簽發(fā)者
                .setIssuedAt(now) // 簽發(fā)時間
                .signWith(signatureAlgorithm, secretKey) //使用HS256對稱加密算法簽名, 第二個參數(shù)為秘鑰
                .setExpiration(expDate);
    }
    /**
     * 創(chuàng)建token
     * @param id
     * @param subject
     * @param ttlMillis
    添加依賴
    2.3.5 認證的實現(xiàn)
    1 配置數(shù)據(jù)庫校驗登錄用戶
    從之前的分析我們可以知道,我們可以自定義一個UserDetailsService,讓SpringSecurity使用我們的
    UserDetailsService。我們自己的UserDetailsService可以從數(shù)據(jù)庫中查詢用戶名和密碼。
    我們先創(chuàng)建一個用戶表, 建表語句如下:
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 設(shè)置過期時間
        return builder.compact();
    }
    /**
     * 生成加密后的秘鑰 secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length,
                "AES");
        return key;
    }
    /**
     * 解析jwt
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }
}

 2.5.1  webUtild 工具類

package com.fashion.utils;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.nio.charset.StandardCharsets;
/**
 * @Author: LQ
 * @Date 2024/8/17 16:56
 * @Description:
 */
@Slf4j
public class WebUtils {
    /**
     *  寫內(nèi)容到客戶端
     * @param response
     * @param obj
     */
    public static void writeResp(HttpServletResponse response,Object obj) {
      try {
          //設(shè)置客戶端的響應(yīng)的內(nèi)容類型
          response.setContentType("application/json;charset=UTF-8");
          //獲取輸出流
          ServletOutputStream outputStream = response.getOutputStream();
          //消除循環(huán)引用
          String result = JSONUtil.toJsonStr(obj);
          SerializerFeature.DisableCircularReferenceDetect);
          outputStream.write(result.getBytes(StandardCharsets.UTF_8));
          outputStream.flush();
          outputStream.close();
      } catch (Exception e) {
          log.error("寫出字符流失敗",e);
      }
    }
}

2.5.2  redis 工具類配置 

package com.fashion.config.datasource;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.web.client.RestTemplate;
/**
 * @Author: LQ
 * @Date 2024/8/17 15:18
 * @Description:
 */
@Configuration
public class RedisConfig {
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        LettuceConnectionFactory lettuceConnectionFactory =
                new LettuceConnectionFactory(new RedisStandaloneConfiguration("127.0.0.1", 6379));
        return lettuceConnectionFactory;
    }
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
        template.setHashKeySerializer(jackson2JsonRedisSerializer());
        template.setHashValueSerializer(jackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    /**
     *  redis 值序列化方式
     * @return
     */
    private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
                new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        // 自動檢測所有類的全部屬性
        objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) ;
        // 此項必須配置,否則會報java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance , ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        // 此設(shè)置默認為true,就是在反序列化遇到未知屬性時拋異常,這里設(shè)置為false,目的為忽略部分序列化對象存入緩存時誤存的其他方法的返回值
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        return jackson2JsonRedisSerializer;
    }
}

2.5.3 spring security 配置

HttpSecurity參數(shù)說明 SecurityFilterChain : 一個表示安全過濾器鏈的對象 http.antMatchers(...).permitAll() 通過 antMatchers 方法,你可以指定哪些請求路徑不 需要進行身份驗證。

http.authorizeRequests() 可以配置請求的授權(quán)規(guī)則。 例 如, .anyRequest().authenticated() 表示任何請求都需要經(jīng)過身份驗證。 http.requestMatchers 表示某個請求不需要進行身份校驗,permitAll 隨意訪問。 http.httpBasic() 配置基本的 HTTP 身份驗證。 http.csrf() 通過 csrf 方法配置 CSRF 保護。 http.sessionManagement() 不會創(chuàng)建會話。這意味著每個請求都是獨立的,不依賴于之前的 請求。適用于 RESTful 風(fēng)格的應(yīng)用。

package com.fashion.config;
import com.fashion.filter.ImgVerifyFilter;
import com.fashion.filter.JwtAuthenticationTokenFilter;
import com.fashion.handler.AnonymousAuthenticationHandler;
import com.fashion.handler.CustomerAccessDeniedHandler;
import com.fashion.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.util.Arrays;
import java.util.List;
/**
 * @Author: LQ
 * @Date 2024/8/13 21:12
 * @Description:
 */
@Configuration
public class SecurityFilterConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Autowired
    private ImgVerifyFilter imgVerifyFilter;
    @Autowired
    private AuthenticationFailureHandler loginFailureHandler;
//    @Autowired
//    private LoginSuccessHandler loginSuccessHandler;
    @Autowired
    private CustomerAccessDeniedHandler customerAccessDeniedHandler;
    @Autowired
    private AnonymousAuthenticationHandler anonymousAuthenticationHandler;
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    private static List<String> EXCLUDE_URL_LIST = Arrays.asList("/static/**","/user/**","/comm/**","/","/favicon.ico");
    /**
     * 登錄時需要調(diào)用AuthenticationManager.authenticate執(zhí)行一次校驗
     *
     */
    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
    // 入口配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
         // 關(guān)閉crsf
         http.csrf(csrf -> csrf.disable());
         // 放行靜態(tài)資源,以及登錄接口放行
         http.authorizeRequests().antMatchers(EXCLUDE_URL_LIST.toArray(new String[]{}))
                 .permitAll()
                 .anyRequest().authenticated();
         // 設(shè)置數(shù)據(jù)源
         http.userDetailsService(userDetailsService);
         // 配置異常過濾器
         //http.formLogin().failureHandler(loginFailureHandler);
        // 其他異常處理
         http.exceptionHandling(config ->
                 {
                     config.accessDeniedHandler(customerAccessDeniedHandler);
                     config.authenticationEntryPoint(anonymousAuthenticationHandler);
                 }
         );
         // 添加圖形驗證碼過濾器
         http.addFilterBefore(imgVerifyFilter, UsernamePasswordAuthenticationFilter.class);
         // jwt token 校驗
         http.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class);
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

2.5.4  web 配置靜態(tài)資源放行等信息

package com.fashion.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
 * @Author: LQ
 * @Date 2024/8/17 16:32
 * @Description:
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {
    /**
     *  放行靜態(tài)資源
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/");
    }
    /**
     *  配置默認首頁地址
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
    }
//    @Override
//    public void addCorsMappings(CorsRegistry registry) {
//        registry.addMapping("/**")
//                .allowedOrigins("*")
//                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
//                .allowedHeaders("*")
//                .allowCredentials(true);
//    }
}

2.5.5  異常類編寫

/**
 * @Author: LQ
 * @Date 2024/8/17 20:29
 * @Description:
 */
public class CustomerAccessException extends AccessDeniedException {
    public CustomerAccessException(String msg) {
        super(msg);
    }
}
/**
 * @Author: LQ
 * @Date 2024/8/17 15:35
 * @Description: 無權(quán)限資源時異常
 */
public class CustomerAuthenticationException extends AuthenticationException {
    public CustomerAuthenticationException(String msg) {
        super(msg);
    }
}

2.5.6  過濾器(圖形驗證碼過濾器)

package com.fashion.filter;
import com.fashion.constants.ComConstants;
import com.fashion.domain.R;
import com.fashion.handler.AnonymousAuthenticationHandler;
import com.fashion.utils.WebUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
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;
/**
 * @Author: LQ
 * @Date 2024/8/17 19:29
 * @Description: 圖像驗證碼過濾器
 */
@Component
@Slf4j
public class ImgVerifyFilter extends OncePerRequestFilter {
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private AnonymousAuthenticationHandler anonymousAuthenticationHandler;
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        String reqUrl = httpServletRequest.getRequestURI();
        log.info("請求url:{}",reqUrl);
        if (ComConstants.LOGIN_URL.equals(reqUrl)) {
            // 開始校驗圖形驗證碼
            Object imgCode = request.getParameter("imageCode");
            Object sessCode = request.getSession().getAttribute(ComConstants.SESSION_IMAGE);
            // 判斷是否和庫里面相等
            log.info("傳過來的驗證碼為:{},session中的為:{}",imgCode,sessCode);
            if (!sessCode.equals(imgCode)) {
                //throw new CustomerAuthenticationException("圖像驗證碼錯誤");
                WebUtils.writeResp(httpServletResponse, R.error().code(400).message("圖像驗證碼失?。?));
                return;
            }
        }
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }
}

2.5.7  jwt 過濾器

  作用:因為禁用了session所以需要將 SecurityContextHolder.getContext() 中

package com.fashion.filter;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.fashion.constants.ComConstants;
import com.fashion.constants.RedisPreConst;
import com.fashion.domain.JwtToken;
import com.fashion.domain.LoginSessionUserInf;
import com.fashion.exception.CustomerAuthenticationException;
import com.fashion.handler.LoginFailureHandler;
import com.fashion.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
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;
/**
 * @Author: LQ
 * @Date 2024/8/17 22:12
 * @Description: jwt 認證
 */
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private LoginFailureHandler loginFailureHandler;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            //獲取當(dāng)前請求的url地址
            String url = request.getRequestURI();
            //如果當(dāng)前請求不是登錄請求,則需要進行token驗證
            if (!url.equals(ComConstants.LOGIN_URL) && !url.startsWith("/user/") && !url.startsWith("/comm")
                    && !url.equals("/") && !url.startsWith("/favicon.ico") && !url.endsWith("js") && !url.endsWith("map")) {
                this.validateToken(request);
            }
        } catch (AuthenticationException e) {
            log.error("jwt異常");
            loginFailureHandler.onAuthenticationFailure(request, response, e);
        }
        //登錄請求不需要驗證token
        doFilter(request, response, filterChain);
    }
    /**
     *  校驗token有效性
     * @param request
     * @throws AuthenticationException
     */
    private void validateToken(HttpServletRequest request) throws
            AuthenticationException {
        //從頭部獲取token信息
        String token = request.getHeader("token");
        //如果請求頭部沒有獲取到token,則從請求的參數(shù)中進行獲取
        if (ObjectUtils.isEmpty(token)) {
            token = request.getParameter("token");
        }
        if (ObjectUtils.isEmpty(token)) {
            throw new CustomerAuthenticationException("token不存在");
        }
        //如果存在token,則從token中解析出用戶名
        Claims claims = null;
        try {
            claims = JwtUtil.parseJWT(token);
        } catch (Exception e) {
            throw new CustomerAuthenticationException("token解析失敗");
        }
        //獲取到主題
        String loginUserString = claims.getSubject();
        //把字符串轉(zhuǎn)成loginUser對象
        JwtToken jwtToken = JSON.parseObject(loginUserString, JwtToken.class);
        // 拿到中間的uuid去庫里面得到用戶信息
        String userTokenPre = String.format(RedisPreConst.LOGIN_TOKEN,jwtToken.getToken());
        // 將用戶信息放到redis中  24小時后過期
        String redisUser = stringRedisTemplate.opsForValue().get(userTokenPre);
        if (ObjectUtils.isEmpty(redisUser)) {
            throw new CustomerAuthenticationException("用戶信息過期,請重新登錄!");
        }
        LoginSessionUserInf loginUser = JSONUtil.toBean(redisUser,LoginSessionUserInf.class);
        //創(chuàng)建身份驗證對象
        UsernamePasswordAuthenticationToken authenticationToken = new
                UsernamePasswordAuthenticationToken(loginUser, null,
                loginUser.getAuthorities());
        //設(shè)置到Spring Security上下文
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    }
}

2.6 自定義登錄接口

2.6.1  登錄controller 接口

package com.fashion.controller;
import com.fashion.domain.R;
import com.fashion.domain.req.LoginUserReq;
import com.fashion.service.UserLoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @Author: LQ
 * @Date 2024/8/17 16:05
 * @Description: 用戶登錄接口
 */
@RestController
@RequestMapping("user/")
public class UserLoginController {
    @Autowired
    private UserLoginService userLoginService;
    /**
     *  用戶登錄
     * @param req
     * @return
     */
    @RequestMapping("login")
    public R userLogin(LoginUserReq req) {
        return userLoginService.login(req);
    }
}

2.6.2  UserLoginService 用戶自定義接口

package com.fashion.service;
import com.fashion.domain.R;
import com.fashion.domain.req.LoginUserReq;
/**
 * @Author: LQ
 * @Date 2024/8/17 16:07
 * @Description: 用戶自定義登錄重寫 ProviderManager的方法進行認證 如果認證通過生成jw
 */
public interface UserLoginService {
    /**
     *  登錄
     * @param userInf
     * @return
     */
   R login(LoginUserReq userInf);
}
@Service
@Slf4j
public class UserLoginServiceImpl implements UserLoginService {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public R login(LoginUserReq userInf) {
        // 1 封裝 authenticationToken 對象,密碼校驗等信息
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userInf.getLoginId(),userInf.getLoginPwd());
        // 2 開始調(diào)用進行校驗
        Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        //3、如果authenticate為空
        if(ObjectUtils.isEmpty(authenticate)){
            throw new CustomerAuthenticationException("登錄失??!");
        }
        //放入的用戶信息
        LoginSessionUserInf loginSessionUserInf = (LoginSessionUserInf)authenticate.getPrincipal();
        //生成jwt,將用戶名+uuid 放進去 這樣jwt 就比較小,更好校驗,將token 作為key 把loginsesionUser信息放到redis中
        JwtToken jwtToken = new JwtToken();
        jwtToken.setLoginId(loginSessionUserInf.getUsername());
        jwtToken.setToken(JwtUtil.getUUID());
        String loginUserString = JSONUtil.toJsonStr(jwtToken);
        //調(diào)用JWT工具類,生成jwt令牌
        String jwtStr = JwtUtil.createJWT(jwtToken.getToken(), loginUserString, JwtUtil.JWT_TTL);
        log.info("jwt token 生成成功:{}",jwtStr);
        String userTokenPre = String.format(RedisPreConst.LOGIN_TOKEN,jwtToken.getToken());
        log.info("用戶拼接后的前綴信息:{}",userTokenPre);
        // 將用戶信息放到redis中  24小時后過期
        stringRedisTemplate.opsForValue().set(userTokenPre, JSONObject.toJSONString(loginSessionUserInf),24, TimeUnit.HOURS);
        // 跳轉(zhuǎn)到頁面
        return R.ok().data("token",jwtStr).message("/main/index");
    }
}

2.6.3  代碼截圖

2.6.4  驗證碼controller

package com.fashion.controller;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.generator.RandomGenerator;
import com.fashion.constants.ComConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.IOException;
/**
 * @Author: LQ
 * @Date 2024/8/17 16:05
 * @Description: 通用接口,不用攔截
 */
@Controller
@RequestMapping("comm/")
@Slf4j
public class ComController {
    @Autowired
    private HttpServletRequest request;
    /**
     *  獲取圖像驗證碼
     * @param response
     */
    @RequestMapping("getVerifyImage")
    public void getVerifyImage(HttpServletResponse response) {
        RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4);
        //定義圖形驗證碼的長、寬、驗證碼位數(shù)、干擾線數(shù)量
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(120, 40,4,19);
        lineCaptcha.setGenerator(randomGenerator);
        lineCaptcha.createCode();
        //設(shè)置背景顏色
        lineCaptcha.setBackground(new Color(249, 251, 220));
        //生成四位驗證碼
        String code = lineCaptcha.getCode();
        log.info("圖形驗證碼生成成功:{}",code);
        request.getSession().setAttribute(ComConstants.SESSION_IMAGE,code);
        response.setContentType("image/jpeg");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        try {
            lineCaptcha.write(response.getOutputStream());
        } catch (IOException e) {
            log.error("圖像驗證碼獲取失?。?,e);
        }
    }
}

2.6.5  登錄首頁

package com.fashion.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
 * @Author: LQ
 * @Date 2024/8/17 22:06
 * @Description: main的主頁
 */
@Controller
@RequestMapping("main/")
@Slf4j
public class MainController {
    @RequestMapping("index")
    public String index() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Object principal = authentication.getPrincipal();
        log.info("我來首頁了,用戶信息:{}",principal);
        return "main";
    }
}

2.7 前端頁面

2.7.1 前端效果

2.7.2 前端代碼

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登錄頁</title>
    <!-- 引入樣式 -->
    <link rel="stylesheet"  rel="external nofollow"  rel="external nofollow" >
    <style type="text/css">
        #app{width: 600px;margin: 28px auto 10px }
        img{cursor: pointer;}
    </style>
</head>
<body>
   <div id="app">
       <el-container>
           <el-header>
               <h2 style="margin-left: 140px;">歡迎進入springsecurity</h2>
           </el-header>
           <el-main>
               <el-form ref="form" :model="form" label-width="140px" :rules="rules">
                   <el-form-item label="用戶名" prop="loginId">
                       <el-input v-model="form.loginId" ></el-input>
                   </el-form-item>
                   <el-form-item label="登錄密碼" prop="loginPwd">
                       <el-input v-model="form.loginPwd"></el-input>
                   </el-form-item>
                   <el-form-item label="圖像驗證碼" prop="imageCode">
                       <el-col :span="10">
                           <el-input v-model="form.imageCode"></el-input>
                       </el-col>
                       <!--<el-col class="line" :span="4"></el-col>-->
                       <el-col :span="5" :offset="1">
                           <img :src="form.imageCodeUrl" @click="getVerifyCode">
                       </el-col>
                  </el-form-item>
                   <!--  <el-form-item label="即時配送">
                        <el-switch v-model="form.delivery"></el-switch>
                    </el-form-item>-->
                   <el-form-item>
                       <el-button type="primary" :loading="status.loading" @click="onSubmit('form')" style="width: 400px;">登錄</el-button>
                      <!-- <el-button>取消</el-button>-->
                   </el-form-item>
               </el-form>
           </el-main>
          <!-- <el-footer>Footer</el-footer>-->
       </el-container>
   </div>
<script type="text/javascript" th:src="@{/static/js/axios.js}"></script>
<script type="text/javascript" th:src="@{/static/js/vue2.js }"></script>
<!-- 引入組件庫 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script type="text/javascript">
    var app = new Vue({
        el:"#app",
        data:{
            form: {
                loginId: 'admin',
                loginPwd: '12345678',
                imageCode: '1111',
                imageCodeUrl: '/comm/getVerifyImage'
            }
            ,status: {
                "loading": false
            }
            ,
            rules: {
                loginId: [
                    { required: true, message: '請?zhí)顚懙卿涃~號', trigger: 'blur' },
                    { min: 3, max: 15, message: '長度在 3 到 15 個字符', trigger: 'blur' }
                ],
                loginPwd: [
                    { required: true, message: '請?zhí)顚懙卿浢艽a', trigger: 'blur' },
                    { min: 3, max: 15, message: '長度在 3 到 15 個字符', trigger: 'blur' }
                ],
                imageCode: [
                    { required: true, message: '請?zhí)顚憟D像驗證碼', trigger: 'blur' },
                    { min: 4, max: 4, message: '長度在4個', trigger: 'blur' }
                ],
            }
        }
        ,methods:{
            onSubmit:function(formName) {
                let that = this;
                that.status.loading = true;
                this.$refs[formName].validate((valid) => {
                    if (valid) {
                        let forData =  JSON.stringify(that.form);
                        let formData = new FormData();
                        formData.append('loginId', that.form.loginId);
                        formData.append('loginPwd', that.form.loginPwd);
                        formData.append('imageCode', that.form.imageCode);
                        //console.log(forData);
                        axios.post("/user/login",
                            formData
                        )
                            .then(function (response) {
                                let resData = response.data;
                                console.log(resData);
                                that.status.loading = false;
                                if (resData.code != '0000') {
                                    that.$message.error(resData.message);
                                    // 刷新驗證碼
                                    that.getVerifyCode();
                                } else {
                                    that.$message({
                                        showClose: true,
                                        message: '登錄成功,稍后進行跳轉(zhuǎn)',
                                        type: 'success'
                                    });
                                    let url = resData.message + "?token=" + resData.data.token
                                    window.location.href = url;
                                }
                            })
                    } else {
                        that.$message.error('請完整填寫信息');
                        return false;
                    }
                });
            },
            resetForm(formName) {
                this.$refs[formName].resetFields();
            }
            ,getVerifyCode: function () {
                console.log("getVerifyCode")
                this.form.imageCodeUrl = '/comm/getVerifyImage?v='+new Date();
            }
        }
    });
</script>
</body>
</html>

2.7.3  登錄成功頁面

2.7.4  htm 代碼

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>主頁菜單</title>
    <!-- 引入樣式 -->
    <link rel="stylesheet"  rel="external nofollow"  rel="external nofollow" >
    <style type="text/css">
    </style>
</head>
<body>
   <div id="app">
       <el-container>
           <el-header>
               <h2 >歡迎進入springsecurity 配置主頁</h2>
           </el-header>
           <el-container>
               <el-aside width="400px">
                   <el-row class="tac">
                       <el-col :span="12">
                           <h5>菜單</h5>
                           <el-menu
                                   default-active="2"
                                   class="el-menu-vertical-demo"
                                   @open="handleOpen"
                                   @close="handleClose">
                               <el-submenu index="1">
                                   <template slot="title">
                                       <i class="el-icon-location"></i>
                                       <span>導(dǎo)航一</span>
                                   </template>
                                   <el-menu-item-group>
                                       <!-- <template slot="title">分組一</template>-->
                                       <el-menu-item index="1-1">選項1</el-menu-item>
                                       <el-menu-item index="1-2">選項2</el-menu-item>
                                   </el-menu-item-group>
                               </el-submenu>
                               <el-menu-item index="2">
                                   <i class="el-icon-menu"></i>
                                   <span slot="title">導(dǎo)航二</span>
                               </el-menu-item>
                               <el-menu-item index="3" disabled>
                                   <i class="el-icon-document"></i>
                                   <span slot="title">導(dǎo)航三</span>
                               </el-menu-item>
                               <el-menu-item index="4">
                                   <i class="el-icon-setting"></i>
                                   <span slot="title">導(dǎo)航四</span>
                               </el-menu-item>
                           </el-menu>
                       </el-col>
                   </el-row>
               </el-aside>
               <el-main>我是內(nèi)容</el-main>
           </el-container>
          <!-- <el-footer>Footer</el-footer>-->
       </el-container>
   </div>
<script type="text/javascript" th:src="@{/static/js/axios.js}"></script>
<script type="text/javascript" th:src="@{/static/js/vue2.js }"></script>
<!-- 引入組件庫 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script type="text/javascript">
    var app = new Vue({
        el:"#app",
        data:{
        }
        ,methods:{
            handleOpen(key, keyPath) {
                console.log(key, keyPath);
            },
            handleClose(key, keyPath) {
                console.log(key, keyPath);
            }
        }
    });
</script>
</body>
</html>

到此這篇關(guān)于springsecurity 登錄認證一(ajax)的文章就介紹到這了,更多相關(guān)springsecurity 登錄認證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java插入排序和希爾排序?qū)崿F(xiàn)思路及代碼

    java插入排序和希爾排序?qū)崿F(xiàn)思路及代碼

    這篇文章主要介紹了插入排序和希爾排序兩種排序算法,文章通過代碼示例和圖解詳細介紹了這兩種排序算法的實現(xiàn)過程和原理,需要的朋友可以參考下
    2025-03-03
  • java序列化與反序列化操作實例分析

    java序列化與反序列化操作實例分析

    這篇文章主要介紹了java序列化與反序列化操作,結(jié)合實例形式分析了java序列化與反序列化的概念與具體實現(xiàn)技巧,需要的朋友可以參考下
    2016-10-10
  • spring?boot集成redisson的最佳實踐示例

    spring?boot集成redisson的最佳實踐示例

    這篇文章主要為大家介紹了spring?boot集成redisson的最佳實踐示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-03-03
  • 在Java 8中將List轉(zhuǎn)換為Map對象方法

    在Java 8中將List轉(zhuǎn)換為Map對象方法

    這篇文章主要介紹了在Java 8中將List轉(zhuǎn)換為Map對象方法,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-11-11
  • JVM中有哪些內(nèi)存區(qū)域及其作用

    JVM中有哪些內(nèi)存區(qū)域及其作用

    這篇文章主要介紹了JVM中有哪些內(nèi)存區(qū)域,分別是用來干什么的,vm內(nèi)又是如何劃分內(nèi)存的呢?這個內(nèi)被加載到了那一塊內(nèi)存中?,需要的朋友可以參考下
    2019-07-07
  • idea左側(cè)的commit框設(shè)置顯示出來方式

    idea左側(cè)的commit框設(shè)置顯示出來方式

    在IDEA中顯示左側(cè)的commit框,首先通過File-Settings-Version Control-Commit進行設(shè)置,然后勾選Use non-modal commit interface完成
    2025-01-01
  • Java中Map實現(xiàn)線程安全的3種方式

    Java中Map實現(xiàn)線程安全的3種方式

    本文主要介紹了Java中Map實現(xiàn)線程安全的3種方式,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Java使用ProcessBuilder?API優(yōu)化流程

    Java使用ProcessBuilder?API優(yōu)化流程

    Java?的?Process?API?為開發(fā)者提供了執(zhí)行操作系統(tǒng)命令的強大功能,這篇文章將詳細介紹如何使用?ProcessBuilder?API?來方便的操作系統(tǒng)命令,需要的可以收藏一下
    2023-06-06
  • Java同時處理多個數(shù)據(jù)的常見方法

    Java同時處理多個數(shù)據(jù)的常見方法

    在Java中,同時處理多個數(shù)據(jù)通常涉及多線程、并發(fā)編程或異步編程,這里我將提供一個使用多線程的示例,因為多線程是處理多個數(shù)據(jù)并行的常見方式,文中有詳細的代碼示例供大家參考,需要的朋友可以參考下
    2024-11-11
  • Java后臺開發(fā)之表單提交之前驗證

    Java后臺開發(fā)之表單提交之前驗證

    這篇文章主要介紹了Java后臺開發(fā)之表單提交之前驗證的實現(xiàn)代碼,非常不錯具有參考借鑒價值,需要的朋友參考下吧
    2017-02-02

最新評論