最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring?Security實現(xiàn)HTTP認證

 更新時間:2022年06月01日 15:48:17   作者:Tony-devj  
本文主要介紹了Spring?Security實現(xiàn)HTTP認證,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧<BR>

Spring Security是一個能夠為基于Spring的企業(yè)應用系統(tǒng)提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應用上下文中配置的Bean,充分利用了Spring IoC(控制反轉),DI(依賴注入)和AOP(面向切面編程)功能,為應用系統(tǒng)提供聲明式的安全訪問控制功能,減少了為企業(yè)系統(tǒng)安全控制編寫大量重復代碼的工作。

前言

除系統(tǒng)內維護的用戶名和密碼認證技術外,Spring Security還支持HTTP層面的認證,包括HTTP基本認證和HTTP摘要認證

一、HTTP基本認證是什么?

HTTP基本認證是在RFC2616中定義的一種認證模式。

二、HTTP基本認證流程

  • 客戶端發(fā)起一條沒有攜帶認證信息的請求。
  • 服務器返回一條401 Unauthorized響應, 并在WWW-Authentication首部說明認證形式, 當進行HTTP基本認證時, WWW-Authentication會被設置為Basic realm=“被保護頁面”。
  • 客戶端收到401 Unauthorized 響應后, 彈出對話框, 詢問用戶名和密碼。 當用戶完成后, 客戶端將用戶名和密碼使用冒號拼接并編碼為Base64形式, 然后放入請求的Authorization首部發(fā)送給服務器。
  • 服務器解碼得到客戶端發(fā)來的用戶名和密碼,并在驗證它們是正確的之后,返回客戶端請求的報文

在這里插入圖片描述

有上面可以看出只需要驗證Authentication即可,因此如果不使用瀏覽器訪問HTTP基本認證保護的頁面,則自行在請求頭中設置Authorization也是可以.

HTTP基本認證是一種無狀態(tài)的認證方式,與表單認證相比,HTTP基本認證是一種基于HTTP層面的認證方式,無法攜帶session,即無法實現(xiàn)Remember-ME功能。另外,用戶名和密碼在傳遞時僅做一次簡單的Base64編碼,幾乎等同于明文傳輸,極易出現(xiàn)密碼被竊聽和重放攻擊等安全性問題,在實際系統(tǒng)開發(fā)中很少使用這種方式來進行安全驗證。 如果有必要,也應使用加密的傳輸層HTTPS來保障安全.

一.Spring Security使用HTTP基本認證

1.創(chuàng)建項目spring-security-http-auth

pom.xml:

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

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

2.創(chuàng)建配置文件WebSecurityConfig

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
    }
}

上面的配置最后添加了httpBasic(),使用http基本認證

3.運行項目

訪問本地項目,http://localhost:8080

會彈出登陸框,我們看到調試工具中返回了401無權限。

我們使用Spring Security提供的默認的用戶名和密碼登陸。

登陸成功后,header中就會有Authorization: Basic dXNlcjo0NWU2NzViOC1hZGYwLTQzNzMtYjA2MS02MGE0YzkzZjA2ZGU=

二.Spring Security HTTP基本認證原理

上面我們實現(xiàn)了HTTP基本認證,我們看看其中Spring Security中是如何做到的?
我們使用HTTP基本認證的時候,在配置類中使用httpBasic()進行處理。
httpBasic方法:

public HttpBasicConfigurer<HttpSecurity> httpBasic() throws Exception {
        return (HttpBasicConfigurer)this.getOrApply(new HttpBasicConfigurer());
    }

上面可以看出,Spring Security進行HTTP基本認證是使用HttpBasicConfigurer配置類進行的。
HttpBasicConfigurer.class:

//構建HttpBasicConfigurer
public HttpBasicConfigurer() {
        this.realmName("Realm");
        LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> entryPoints = new LinkedHashMap();
        entryPoints.put(X_REQUESTED_WITH, new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
        DelegatingAuthenticationEntryPoint defaultEntryPoint = new DelegatingAuthenticationEntryPoint(entryPoints);
        defaultEntryPoint.setDefaultEntryPoint(this.basicAuthEntryPoint);
        this.authenticationEntryPoint = defaultEntryPoint;
    }
//進行配置
public void configure(B http) {
		//進行認證管理
        AuthenticationManager authenticationManager = (AuthenticationManager)http.getSharedObject(AuthenticationManager.class);
        //聲明basic認證攔截器
        BasicAuthenticationFilter basicAuthenticationFilter = new BasicAuthenticationFilter(authenticationManager, this.authenticationEntryPoint);
        if (this.authenticationDetailsSource != null) {
            basicAuthenticationFilter.setAuthenticationDetailsSource(this.authenticationDetailsSource);
        }
		//注冊一個RememberMeServices
        RememberMeServices rememberMeServices = (RememberMeServices)http.getSharedObject(RememberMeServices.class);
        if (rememberMeServices != null) {
			//設置rememberMeServices      
            basicAuthenticationFilter.setRememberMeServices(rememberMeServices);
        }
		//申明basicAuthenticationFilter過濾器
        basicAuthenticationFilter = (BasicAuthenticationFilter)this.postProcess(basicAuthenticationFilter);
        http.addFilter(basicAuthenticationFilter);
    }

上面聲明BasicAuthenticationFilter并添加到攔截器鏈中
BasicAuthenticationFilter.class:

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
        	//獲取token
            UsernamePasswordAuthenticationToken authRequest = this.authenticationConverter.convert(request);
            //authRequest為空直接放行
            if (authRequest == null) {
                this.logger.trace("Did not process authentication request since failed to find username and password in Basic Authorization header");
                chain.doFilter(request, response);
                return;
            }
			//獲取用戶名
            String username = authRequest.getName();
            this.logger.trace(LogMessage.format("Found username '%s' in Basic Authorization header", username));
            if (this.authenticationIsRequired(username)) {
                Authentication authResult = this.authenticationManager.authenticate(authRequest);
                //創(chuàng)建上下文
                SecurityContext context = SecurityContextHolder.createEmptyContext();
                context.setAuthentication(authResult);
                //設置響應的上下文
                SecurityContextHolder.setContext(context);
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
                }
				
                this.rememberMeServices.loginSuccess(request, response, authResult);
                this.onSuccessfulAuthentication(request, response, authResult);
            }
        } catch (AuthenticationException var8) {
            SecurityContextHolder.clearContext();
            this.logger.debug("Failed to process authentication request", var8);
            this.rememberMeServices.loginFail(request, response);
            this.onUnsuccessfulAuthentication(request, response, var8);
            if (this.ignoreFailure) {
                chain.doFilter(request, response);
            } else {
                this.authenticationEntryPoint.commence(request, response, var8);
            }

            return;
        }

        chain.doFilter(request, response);
    }

BasicAuthenticationEntryPoint返回進行響應的處理

 public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
 		//添加響應響應頭
        response.addHeader("WWW-Authenticate", "Basic realm=\"" + this.realmName + "\"");
        response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
    }

三.HTTP摘要認證是什么?

HTTP摘要認證和HTTP基本認證一樣,也是在RFC2616中定義的認證模式,RFC2617專門對這兩種認證模式做了規(guī)定。與 HTTP 基本認證相比,HTTP 摘要認證使用對通信雙方都可知的口令進行校驗,且最終的傳輸數據并非明文形式。

摘要認證是一種協(xié)議規(guī)定的Web服務器用來同網頁瀏覽器進行認證信息協(xié)商的方法。它在密碼發(fā)出前,先對其應用哈希函數,這相對于HTTP基本認證發(fā)送明文而言,更安全。

從技術上講,摘要認證是使用隨機數來阻止進行密碼分析的MD5加密哈希函數應用。
HTTP摘要認證流程:

HTTP摘要認證中的相關參數:

  • username: 用戶名。
  • password: 用戶密碼。
  • realm: 認證域, 由服務器返回。
  • opaque: 透傳字符串, 客戶端應原樣返回。
  • method: 請求的方法。
  • nonce: 由服務器生成的隨機字符串。
  • nc: 即nonce-count, 指請求的次數, 用于計數, 防止重放攻擊。 qop被指定時, nc也必須被指定。
  • cnonce: 客戶端發(fā)給服務器的隨機字符串, qop被指定時, cnonce也必須被指定。
  • qop: 保護級別, 客戶端根據此參數指定摘要算法。 若取值為auth, 則只進行身份驗證; 若取
  • 值為auth-int, 則還需要校驗內容完整性。
  • uri: 請求的uri。
  • response:客戶端根據算法算出的摘要值。
  • algorithm:摘要算法, 目前僅支持MD5。
  • entity-body:頁面實體,非消息實體,僅在auth-int中支持。
  • 通常服務器攜帶的數據包括realm、 opaque、 nonce、 qop等字段, 如果客戶端需要做出驗證回應,就必須按照一定的算法計算得到一些新的數據并一起返回。

四.Spring Security使用HTTP摘要認證流程?

在Spring Security中沒有像HTTP基礎認證那樣,通過httpBasic()方法進行集成HTTP摘要認證,但是Spring Security提供了像BasicAuthenticationEntryPoint一樣的DigestAuthenticationEntryPoint.就是我們需要將DigestAuthenticationEntryPoint添加到filter過濾器中去處理。
代碼如下:
WebSecurityConfig類:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DigestAuthenticationEntryPoint digestAuthenticationEntryPoint;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and()
                .exceptionHandling()
                .authenticationEntryPoint(digestAuthenticationEntryPoint)
                .and().addFilter(digestAuthenticationFilter());
    }

    public DigestAuthenticationFilter digestAuthenticationFilter(){
        DigestAuthenticationFilter digestAuthenticationFilter = new DigestAuthenticationFilter();
        digestAuthenticationFilter.setUserDetailsService(userDetailsService);
        digestAuthenticationFilter.setAuthenticationEntryPoint(digestAuthenticationEntryPoint);
        return digestAuthenticationFilter;
    }

}

申明DigestAuthenticationEntryPointBean:

@Bean
    public DigestAuthenticationEntryPoint digestAuthenticationEntryPoint(){
        DigestAuthenticationEntryPoint digestAuthenticationEntryPoint = new DigestAuthenticationEntryPoint();
        digestAuthenticationEntryPoint.setRealmName("realName");
        digestAuthenticationEntryPoint.setKey("tony");
        return digestAuthenticationEntryPoint;
    }
@Bean
    public DigestAuthenticationEntryPoint digestAuthenticationEntryPoint(){
        DigestAuthenticationEntryPoint digestAuthenticationEntryPoint = new DigestAuthenticationEntryPoint();
        digestAuthenticationEntryPoint.setRealmName("realm");
        digestAuthenticationEntryPoint.setKey("tony");
        return digestAuthenticationEntryPoint;
    }

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("tony").password("123456").roles("admin").build());
        return manager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

運行項目

訪問主頁,http://localhost:8080,返回如下頁面:

我們輸入用戶名和密碼登陸。

當長時間未登錄,隨機字符串到期了也登陸不上。
默認的過期時間為300s,我們可以通過設置時間。
DigestAuthenticationEntryPoint中realmName和key是必須要設置的。
相關源碼:

public void afterPropertiesSet() {
        Assert.hasLength(this.realmName, "realmName must be specified");
        Assert.hasLength(this.key, "key must be specified");
    }
    
 public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
 		//計算過期時間
        long expiryTime = System.currentTimeMillis() + (long)(this.nonceValiditySeconds * 1000);
        //計算簽名值
        String signatureValue = DigestAuthUtils.md5Hex(expiryTime + ":" + this.key);
        //隨機字符串
        String nonceValue = expiryTime + ":" + signatureValue;
        //隨機字符串base64
        String nonceValueBase64 = new String(Base64.getEncoder().encode(nonceValue.getBytes()));
        String authenticateHeader = "Digest realm=\"" + this.realmName + "\", qop=\"auth\", nonce=\"" + nonceValueBase64 + "\"";
        if (authException instanceof NonceExpiredException) {
            authenticateHeader = authenticateHeader + ", stale=\"true\"";
        }

        logger.debug(LogMessage.format("WWW-Authenticate header sent to user agent: %s", authenticateHeader));
        response.addHeader("WWW-Authenticate", authenticateHeader);
        response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
    }

進行處理的時候使用DigestAuthenticationFilter進行處理

public void afterPropertiesSet() {
		//必須設置userDetailsService
        Assert.notNull(this.userDetailsService, "A UserDetailsService is required");
        //必須設置authenticationEntryPoint
        Assert.notNull(this.authenticationEntryPoint, "A DigestAuthenticationEntryPoint is required");
    }

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Digest ")) {
            logger.debug(LogMessage.format("Digest Authorization header received from user agent: %s", header));
            DigestAuthenticationFilter.DigestData digestAuth = new DigestAuthenticationFilter.DigestData(header);

            try {
				//驗證并且解密
                digestAuth.validateAndDecode(this.authenticationEntryPoint.getKey(), this.authenticationEntryPoint.getRealmName());
            } catch (BadCredentialsException var11) {
                this.fail(request, response, var11);
                return;
            }
            //緩存
            boolean cacheWasUsed = true;
            //緩存用戶數據
            UserDetails user = this.userCache.getUserFromCache(digestAuth.getUsername());

            String serverDigestMd5;
            try {
                if (user == null) {
                    cacheWasUsed = false;
                    user = this.userDetailsService.loadUserByUsername(digestAuth.getUsername());
                    if (user == null) {
                        throw new AuthenticationServiceException("AuthenticationDao returned null, which is an interface contract violation");
                    }

                    this.userCache.putUserInCache(user);
                }
				//服務器md5摘要
                serverDigestMd5 = digestAuth.calculateServerDigest(user.getPassword(), request.getMethod());
                if (!serverDigestMd5.equals(digestAuth.getResponse()) && cacheWasUsed) {
                    logger.debug("Digest comparison failure; trying to refresh user from DAO in case password had changed");
                    user = this.userDetailsService.loadUserByUsername(digestAuth.getUsername());
                    this.userCache.putUserInCache(user);
                    serverDigestMd5 = digestAuth.calculateServerDigest(user.getPassword(), request.getMethod());
                }
            } catch (UsernameNotFoundException var12) {
                String message = this.messages.getMessage("DigestAuthenticationFilter.usernameNotFound", new Object[]{digestAuth.getUsername()}, "Username {0} not found");
                this.fail(request, response, new BadCredentialsException(message));
                return;
            }

            String message;
            if (!serverDigestMd5.equals(digestAuth.getResponse())) {
                logger.debug(LogMessage.format("Expected response: '%s' but received: '%s'; is AuthenticationDao returning clear text passwords?", serverDigestMd5, digestAuth.getResponse()));
                message = this.messages.getMessage("DigestAuthenticationFilter.incorrectResponse", "Incorrect response");
                this.fail(request, response, new BadCredentialsException(message));
            } else if (digestAuth.isNonceExpired()) {
                message = this.messages.getMessage("DigestAuthenticationFilter.nonceExpired", "Nonce has expired/timed out");
                this.fail(request, response, new NonceExpiredException(message));
            } else {
                logger.debug(LogMessage.format("Authentication success for user: '%s' with response: '%s'", digestAuth.getUsername(), digestAuth.getResponse()));
                Authentication authentication = this.createSuccessfulAuthentication(request, user);
                SecurityContext context = SecurityContextHolder.createEmptyContext();
                context.setAuthentication(authentication);
                SecurityContextHolder.setContext(context);
                chain.doFilter(request, response);
            }
        } else {
            chain.doFilter(request, response);
        }
    }

DigestData為摘要數據:

 private class DigestData {
//用戶名
    private final String username;
    //認證域
    private final String realm;
    //隨機字符串
    private final String nonce;
    private final String uri;
    private final String response;
    //保護級別
    private final String qop;
    //即nonce-count, 指請求的次數, 用于計數, 防止重放攻擊
    private final String nc;
    private final String cnonce;
    private final String section212response;
    private long nonceExpiryTime;

    DigestData(String header) {
        this.section212response = header.substring(7);
        String[] headerEntries = DigestAuthUtils.splitIgnoringQuotes(this.section212response, ',');
        Map<String, String> headerMap = DigestAuthUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
        this.username = (String)headerMap.get("username");
        this.realm = (String)headerMap.get("realm");
        this.nonce = (String)headerMap.get("nonce");
        this.uri = (String)headerMap.get("uri");
        this.response = (String)headerMap.get("response");
        this.qop = (String)headerMap.get("qop");
        this.nc = (String)headerMap.get("nc");
        this.cnonce = (String)headerMap.get("cnonce");
        DigestAuthenticationFilter.logger.debug(LogMessage.format("Extracted username: '%s'; realm: '%s'; nonce: '%s'; uri: '%s'; response: '%s'", new Object[]{this.username, this.realm, this.nonce, this.uri, this.response}));
    }
   //驗證和解密
    void validateAndDecode(String entryPointKey, String expectedRealm) throws BadCredentialsException {
        if (this.username != null && this.realm != null && this.nonce != null && this.uri != null && this.response != null) {
            if ("auth".equals(this.qop) && (this.nc == null || this.cnonce == null)) {
                DigestAuthenticationFilter.logger.debug(LogMessage.format("extracted nc: '%s'; cnonce: '%s'", this.nc, this.cnonce));
                throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.missingAuth", new Object[]{this.section212response}, "Missing mandatory digest value; received header {0}"));
            } else if (!expectedRealm.equals(this.realm)) {
                throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.incorrectRealm", new Object[]{this.realm, expectedRealm}, "Response realm name '{0}' does not match system realm name of '{1}'"));
            } else {
                byte[] nonceBytes;
                try {
                    nonceBytes = Base64.getDecoder().decode(this.nonce.getBytes());
                } catch (IllegalArgumentException var8) {
                    throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.nonceEncoding", new Object[]{this.nonce}, "Nonce is not encoded in Base64; received nonce {0}"));
                }

                String nonceAsPlainText = new String(nonceBytes);
                String[] nonceTokens = StringUtils.delimitedListToStringArray(nonceAsPlainText, ":");
                if (nonceTokens.length != 2) {
                    throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.nonceNotTwoTokens", new Object[]{nonceAsPlainText}, "Nonce should have yielded two tokens but was {0}"));
                } else {
                    try {
                        this.nonceExpiryTime = new Long(nonceTokens[0]);
                    } catch (NumberFormatException var7) {
                        throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.nonceNotNumeric", new Object[]{nonceAsPlainText}, "Nonce token should have yielded a numeric first token, but was {0}"));
                    }

                    String expectedNonceSignature = DigestAuthUtils.md5Hex(this.nonceExpiryTime + ":" + entryPointKey);
                    if (!expectedNonceSignature.equals(nonceTokens[1])) {
                        throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.nonceCompromised", new Object[]{nonceAsPlainText}, "Nonce token compromised {0}"));
                    }
                }
            }
        } else {
            throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.missingMandatory", new Object[]{this.section212response}, "Missing mandatory digest value; received header {0}"));
        }
    }
//計算服務摘要
    String calculateServerDigest(String password, String httpMethod) {
    	//生產摘要
        return DigestAuthUtils.generateDigest(DigestAuthenticationFilter.this.passwordAlreadyEncoded, this.username, this.realm, password, httpMethod, this.uri, this.qop, this.nonce, this.nc, this.cnonce);
    }
//判斷隨機數是否到期
    boolean isNonceExpired() {
        long now = System.currentTimeMillis();
        return this.nonceExpiryTime < now;
    }

    String getUsername() {
        return this.username;
    }

    String getResponse() {
        return this.response;
    }
}

到此這篇關于Spring Security實現(xiàn)HTTP認證的文章就介紹到這了,更多相關Spring Security HTTP認證內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • SpringCloud容器化服務發(fā)現(xiàn)及注冊實現(xiàn)方法解析

    SpringCloud容器化服務發(fā)現(xiàn)及注冊實現(xiàn)方法解析

    這篇文章主要介紹了SpringCloud容器化服務發(fā)現(xiàn)及注冊實現(xiàn)方法解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-08-08
  • 老生常談Java中instanceof關鍵字的理解

    老生常談Java中instanceof關鍵字的理解

    java 中的instanceof 運算符是用來在運行時指出對象是否是特定類的一個實例。這篇文章主要介紹了老生常談Java中instanceof關鍵字的理解,需要的朋友可以參考下
    2018-10-10
  • SpringBoot使用自定義json解析器的使用方法

    SpringBoot使用自定義json解析器的使用方法

    本篇文章主要介紹了SpringBoot使用自定義json解析器的使用方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04
  • 使用Feign調用第三方http接口

    使用Feign調用第三方http接口

    這篇文章主要介紹了使用Feign調用第三方http接口,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • 微服務領域Spring Boot自動伸縮的實現(xiàn)方法

    微服務領域Spring Boot自動伸縮的實現(xiàn)方法

    這篇文章主要給大家介紹了關于微服務領域Spring Boot自動伸縮的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家學習或者使用spring boot具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2018-10-10
  • java實現(xiàn)二維數組轉置的方法示例

    java實現(xiàn)二維數組轉置的方法示例

    這篇文章主要介紹了java實現(xiàn)二維數組轉置的方法,結合實例形式詳細分析了java二維數組轉置的原理、實現(xiàn)步驟與相關操作技巧,需要的朋友可以參考下
    2017-10-10
  • 談談為JAXB和response設置編碼,解決wechat4j中文亂碼的問題

    談談為JAXB和response設置編碼,解決wechat4j中文亂碼的問題

    中文亂碼是每個程序員都會遇到的問題,本篇文章主要介紹了談談為JAXB和response設置編碼,解決wechat4j中文亂碼的問題,具有一定的參考價值,有興趣的可以了解一下。
    2016-12-12
  • java中JVM中如何存取數據和相關信息詳解

    java中JVM中如何存取數據和相關信息詳解

    這篇文章主要介紹了JVM中如何存取數據和相關信息詳解,Java源代碼文件(.java后綴)會被Java編譯器編譯為字節(jié)碼文件,然后由JVM中的類加載器加載各個類的字節(jié)碼文件,加載完畢之后,交由JVM執(zhí)行引擎執(zhí)行。JVM中怎么存取數據和相關信息呢?,需要的朋友可以參考下
    2019-06-06
  • JAVA annotation入門基礎

    JAVA annotation入門基礎

    以下是JAVA annotation入門基礎,新手朋友們可以過來參考下。希望對你有所幫助
    2013-08-08
  • Java OpenCV圖像處理之仿射變換,透視變換,旋轉詳解

    Java OpenCV圖像處理之仿射變換,透視變換,旋轉詳解

    這篇文章主要為大家詳細介紹了Java OpenCV圖像處理中仿射變換,透視變換,旋轉的實現(xiàn),文中的示例代碼講解詳細,快跟隨小編一起學習一下
    2022-10-10

最新評論

杨浦区| 迁西县| 广平县| 红桥区| 门头沟区| 东乡县| 内丘县| 房山区| 桓仁| 辽宁省| 潞城市| 科技| 藁城市| 健康| 竹北市| 乐陵市| 南召县| 台北市| 湟源县| 南丹县| 门头沟区| 合水县| 双峰县| 平乐县| 临江市| 汕头市| 富川| 嫩江县| 九江县| 北京市| 杭锦旗| 迁西县| 平利县| 海兴县| 曲沃县| 交口县| 鄂尔多斯市| 泌阳县| 巴彦淖尔市| 阿拉善盟| 北票市|