[JAVA] J'ai essayé d'implémenter un client OAuth avec Spring Boot / Security (connexion LINE)

1. Paramètre du serveur d'autorisation (LINE)

LINE Developers: https://developers.line.biz/ja

・ Type de canal: Connexion en ligne ・ Types d'applications: application Web -URL de rappel: http : // localhost: 8080 / redirect (URL de destination de redirection après authentification)

2. Implémentation client (Spring Boot / Security)

2-1. Ajout de bibliothèques dépendantes

pom.xml


    <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.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
        <dependency>
        <groupId>org.springframework.security.oauth.boot</groupId>
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    </dependency>
2-2. Configuration de application.yml

Sur la base du contenu suivant, passez l'utilisateur à l'écran d'authentification LINE et appelez l'API LINE pour acquérir des informations utilisateur

security.oauth2.client.clientId  - Channnel ID security.oauth2.client.clientSecret  - Channnel Secret security.oauth2.client.accessTokenUri --Endpoint à appeler lors de l'émission du jeton d'accès security.oauth2.client.userAuthorizationUri

application.yml


spring:
  main:
    allow-bean-definition-overriding: true
security:
  oauth2:
    client:
      clientId: [CLIENT_ID]
      clientSecret: [CLIENT_SECRET]
      accessTokenUri: https://api.line.me/oauth2/v2.1/token
      userAuthorizationUri: https://access.line.me/oauth2/v2.1/authorize
      use-current-uri: false
      pre-established-redirect-uri: https://localhost:8443/redirect
      authorized-grant-types:
      - authorization_code
      - refresh_token
      clientAuthenticationScheme: form
      scope:
        - openid
        - email
        - profile
    resource:
      userInfoUri: https://api.line.me/oauth2/v2.1/userinfo
      preferTokenInfo: true
server:
  port: 8443
  ssl:
    key-store: [.chemin du fichier p12]
    key-store-password: [mot de passe]
    keyStoreType: PKCS12
    keyAlias: MyAlias
logging:
  level:
     org.springframework.web: DEBUG    
2-3. Obtenir des informations variables depuis application.yml

ConfigComponent.java


import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component
@PropertySource("classpath:application.yml")
@ConfigurationProperties(prefix = "security.oauth2.client")
public class ConfigComponent {

    private String accessTokenUri;

    private String clientId;

    private String clientSecret;
    
    @Value("${security.oauth2.client.pre-established-redirect-uri}")
    private String redirecturi;
    
    @Value("${security.oauth2.resource.userInfoUri}")
    private String userInfoUri;

    public String getAccessTokenUri() {
        return this.accessTokenUri;
    }
    
    public void setAccessTokenUri(String accessTokenUri) {
        this.accessTokenUri = accessTokenUri;
    }

    public String getClientId() {
        return this.clientId;
    }
    
    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public String getRedirecturi() {
        return this.redirecturi;
    }

    public void setRedirecturi(String redirecturi) {
        this.redirecturi = redirecturi;
    }
    
    public String getClientSecret() {
        return this.clientSecret;
    }
    public void setClientSecret(String clientSecret) {
        this.clientSecret = clientSecret;
    }
    
    public String getUserInfoUri() {
        return this.userInfoUri;
    }
    public void setUserInfoUri(String userInfoUri) {
        this.userInfoUri = userInfoUri;
    }
}
2-4. Enregistrement @Bean

--Enregistrez la classe d'acquisition d'informations variables définie ci-dessus dans le bean

ConfigBean.java


package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig {

    @Bean
    public ConfigComponent configComponent() {
        return new ConfigComponent();
    }
    
    @Bean
    public LoginRestClient loginRestClient() {
        return new LoginRestClient();
    }
}
2-5. Exécuter l'authentification OAuth lorsque l'utilisateur accède au chemin spécifié (/ login)

SecurityConfig.java


import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;

@EnableWebSecurity
@EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                            "/images/**",
                            "/css/**",
                            "/javascript/**",
                            "/webjars/**",
                            "/favicon.ico");
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	http
	    	.authorizeRequests()
		        .antMatchers("/redirect/**")
		        	.permitAll()
		        .and()
	        .authorizeRequests()
		        .antMatchers("/login/**")
		        .authenticated();
    }   
}
2-5. Définition de @Controller

--Une fois l'authentification terminée, obtenez le code d'autorisation et exécutez la demande d'émission de jeton d'accès / d'acquisition d'informations utilisateur. -Injecter la classe LoginRestClient avec @Autowired --Passez l'objet obtenu au modèle Thymeleaf avec la classe ModelAndView

LoginController.java


import static java.util.Objects.requireNonNull;

import java.io.IOException;
import java.util.stream.IntStream;

import com.example.demo.LoginRestClient;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class LoginController {
	
	@Autowired
	LoginRestClient loginRestClient;
	
	@RequestMapping(value = "/redirect", method = RequestMethod.GET)
	public String getToken(@RequestParam("code") String code,
			HttpServletRequest req, HttpServletResponse res) throws IOException{
	  LineToken responseToken = loginRestClient.getToken(code);
	  String accessTK = responseToken.getAccessTK();
	  String refreshTK = responseToken.getRefreshTK();
	  String idTK = responseToken.getIdTK();
	  
	  Cookie accessTKCookie = new Cookie("access_token", accessTK);
	  accessTKCookie.setSecure(true);
	  Cookie refreshTKCookie = new Cookie("refresh_token", refreshTK);
	  refreshTKCookie.setSecure(true);
	  Cookie idTKCookie = new Cookie("id_token", idTK);
	  idTKCookie.setSecure(true);
	  
	  res.addCookie(accessTKCookie);
	  res.addCookie(refreshTKCookie);
	  res.addCookie(idTKCookie);
	  
	  return "redirect:/";
    }

    @RequestMapping(value = "/userinfo", method = RequestMethod.GET)
    public ModelAndView getSubInfo(ModelAndView mv, HttpServletRequest req) throws IOException {
    	Cookie cookies[] = req.getCookies();
    	for(Cookie c: cookies) {
    		if(c.getName().equals("access_token")) {
    			System.err.println(c.getValue());
    			JSONObject userInfo =
    					new JSONObject(loginRestClient.getUserInfo(c.getValue()).getBody());

    		  	String sub = userInfo.getString("sub");
    		  	String name = userInfo.getString("name");
    		  	String picture = userInfo.getString("picture");

    			mv.addObject("sub", sub);
    			mv.addObject("name", name);
    			mv.addObject("picture", picture);
    			mv.setViewName("userinfo");
    		}
    	}
    	return mv;
    	
    	}
    
	@RequestMapping(value = "/", method = RequestMethod.GET)
 	public ModelAndView getTopPage(ModelAndView mv, HttpServletRequest req) {
    	Cookie cookies[] = req.getCookies();
    	for(Cookie c: cookies) {
    		if(c.getName().equals("access_token")) {
    			mv.addObject("access_token", c.getValue());
    		} else if (c.getName().equals("refresh_token")) {
    			mv.addObject("refresh_token", c.getValue());
    		} else if (c.getName().equals("id_token")) {
    			mv.addObject("id_token", c.getValue());
    		}
    	}

    	mv.setViewName("index");
    	
    	return mv;
     }
}
2-6. Classe d'exécution de la demande pour l'émission d'un jeton d'accès / l'acquisition d'informations utilisateur

LoginRestClient.java


import java.io.IOException;
import java.util.Map;
import java.util.Objects;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

@Component
public class LoginRestClient {
	
	final static RestTemplate rs = new RestTemplate();

	@Autowired
	private ConfigComponent cc;
	
    public LineToken getToken(String code) throws IOException {
    	code = Objects.requireNonNull(code);
    	HttpHeaders headers = new HttpHeaders();
    	headers.setBasicAuth(cc.getClientId(), cc.getClientSecret());
    	headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    	
    	MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
    	params.add("code", code);
    	params.add("redirect_uri", cc.getRedirecturi());
    	params.add("grant_type", "authorization_code");
    	
    	HttpEntity<MultiValueMap<String, Object>> requestEntity 
    		= new HttpEntity<>(params, headers);
    	
        LineToken response = rs
                .postForObject(cc.getAccessTokenUri(), requestEntity, LineToken.class);
    	
    	return response;
    }
    
    public ResponseEntity<String> getUserInfo(String accessToken) throws IOException {
    	accessToken = Objects.requireNonNull(accessToken);
    	HttpHeaders headers = new HttpHeaders();
    	headers.setBearerAuth(accessToken);
    	
    	HttpEntity<MultiValueMap<String, Object>> requestEntity 
			= new HttpEntity<>(headers);
    	
    	 ResponseEntity<String> response =
    			 rs.exchange(cc.getUserInfoUri(), HttpMethod.GET, requestEntity, String.class);
    	
    	System.err.println(response);
    	
    	return response;
    }

}
2-7. Conversion POJO du jeton d'accès / jeton d'actualisation / jeton ID

LineToken.java


import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

import static java.util.Objects.requireNonNull;

public final class LineToken {
    public final String accessToken;
    public final String idToken;
    public final String refreshToken;

    @JsonCreator
    LineToken(
        @JsonProperty("access_token") String accessToken,
        @JsonProperty("id_token") String idToken,
        @JsonProperty("refresh_token") String refreshToken
    ) {
        this.accessToken = requireNonNull(accessToken, "accessToken");
        this.idToken = requireNonNull(idToken, "idToken");
        this.refreshToken = requireNonNull(refreshToken, "refreshToken");
    }
    
    public String getAccessTK() {return this.accessToken;}
    public String getIdTK() {return this.idToken;}
    public String getrefreshTK() {return this.refreshToken;}
    
}

3. Vérification

3-1. Accédez à https : // localhost: 8443 / login
3-2. Redirection vers l'écran de connexion LINE
キャプチャ.PNG
3-3. Exécution de la connexion
3-4. Décoder id_token

Étant donné que le jeton d'identification est retourné au format JWT, Base64 décode la partie en-tête / charge utile et fait référence au contenu.

entête: {"typ":"JWT","alg":"HS256"}

charge utile: {"iss":"https://access.line.me","sub":"**********","aud":"1653797412","exp":1579957272,"iat":1579953672,"amr":["linesso"],"name":"Yu","picture":"https://profile.line-scdn.net/0ho4KGrFFFMBt2PhvCq9pPTEp7PnYBEDZTDg98LVpsanhTXn4aTFx5Lwdpbn9SWnEYHwgtfFs3Znhd"}

Nom de la revendication Réclamer le contenu
iss Émetteur de jetons(ISSuer)
sub Cible des jetons(SUBubect)
aud Destinataire du jeton(AUDience)
exp Date d'expiration du jeton(EXPiration time)
iat Horodatage d'émission du jeton(Issued-AT)

Recommended Posts

J'ai essayé d'implémenter un client OAuth avec Spring Boot / Security (connexion LINE)
Partie 1: Essayez d'utiliser la connexion OAuth 2.0 prise en charge par Spring Security 5 avec Spring Boot
Connexion SNS avec Spring Boot
Fonction de connexion avec Spring Security
Mise en œuvre de la fonction d'authentification avec Spring Security ②
Implémentez la fonction d'authentification avec Spring Security ③
Créez une application avec Spring Boot 2
Mise en œuvre de la fonction d'authentification avec Spring Security ①
Créez une application avec Spring Boot
J'ai essayé GraphQL avec Spring Boot
[Java] Intégration LINE avec Spring Boot
J'ai essayé Flyway avec Spring Boot
J'ai essayé l'initialisation paresseuse avec Spring Boot 2.2.0
Obtenez une authentification BASIC avec Spring Boot + Spring Security
[LINE BOT] J'ai créé un Ramen BOT avec Java (Maven) + Heroku + Spring Boot (1)
Hash des mots de passe avec Spring Boot + Spring Security (avec sel, avec étirement)
Essayez l'authentification LDAP avec Spring Security (Spring Boot) + OpenLDAP
Essayez d'implémenter la fonction de connexion avec Spring Boot
Je voulais classer la botte à ressort dans un multi-projet
[Introduction à Spring Boot] Fonction d'authentification avec Spring Security
Créez un serveur Spring Cloud Config en toute sécurité avec Spring Boot 2.0
J'ai créé un domaine api avec Spring Framework. Partie 2
Mémo d'utilisation de Spring Security: coopération avec Spring MVC et Boot
Spring Boot avec les paramètres du filtre de sécurité Spring et les points addictifs
Implémentez une API Rest simple avec Spring Security avec Spring Boot 2.0
SSO avec GitHub OAuth dans l'environnement Spring Boot 1.5.x
Créez un site de démonstration simple avec Spring Security avec Spring Boot 2.1
J'ai créé un domaine api avec Spring Framework. Partie 1
J'ai écrit un test avec Spring Boot + JUnit 5 maintenant
Télécharger avec Spring Boot
Avec Spring Boot, hachez le mot de passe et utilisez l'enregistrement des membres et la sécurité Spring pour implémenter la fonction de connexion.
J'ai essayé le guide d'introduction de Spring Boot [Accès aux données avec JPA]
J'ai essayé de démarrer avec Swagger en utilisant Spring Boot
Implémentez une API Rest simple avec Spring Security & JWT avec Spring Boot 2.0
Connectez-vous avec HttpServletRequest # login dans Spring Security dans l'environnement Servlet 3.x
Générer un code à barres avec Spring Boot
Hello World avec Spring Boot
Implémenter GraphQL avec Spring Boot
Démarrez avec Spring Boot
Bonjour tout le monde avec Spring Boot!
Exécutez LIFF avec Spring Boot
Téléchargement de fichiers avec Spring Boot
Spring Boot commençant par copie
Spring Boot à partir de Docker
Hello World avec Spring Boot
Définir des cookies avec Spring Boot
Utiliser Spring JDBC avec Spring Boot
Ajouter un module avec Spring Boot
Premiers pas avec Spring Boot
[Spring Boot] Envoyer un e-mail
Essayez d'utiliser Spring Boot Security
Créer un micro service avec Spring Boot
Envoyer du courrier avec Spring Boot
Intégration API de Java avec Jersey Client
Java pour jouer avec Function
Client GraphQL commençant par Ruby
J'ai essayé d'implémenter un client OAuth avec Spring Boot / Security (connexion LINE)
[Java EE] Implémenter le client avec WebSocket
Double soumission de mesures avec Play Framework
Authentification Oauth2 avec Spring Cloud Gateway
Tweak Markdown avec Java flexmark-java
Traitement d'image: jouons avec l'image
Une erreur 404 se produit lors du test de l'authentification par formulaire avec Spring Security
02. J'ai créé une API pour me connecter de Spring Boot à MySQL (My Batis)
Partie 4: Personnalisez le comportement de la connexion OAuth 2.0 prise en charge par Spring Security 5
J'ai créé un formulaire de recherche simple avec Spring Boot + GitHub Search API.
Liaison de base de données avec doma2 (Spring boot)