LINE Developers: https://developers.line.biz/ja
・ Channel Type: LINE Login ・ App Types: Web app -Callback URL: http : // localhost: 8080 / redirect (URL to redirect after authentication)
2-1. Addition of dependent libraries
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>
Based on the following contents, transition the user to the LINE authentication screen and call the LINE API to acquire user information
security.oauth2.client.clientId - Channnel ID security.oauth2.client.clientSecret - Channnel Secret security.oauth2.client.accessTokenUri --Endpoint to be called when issuing an access token security.oauth2.client.userAuthorizationUri --Authentication destination endpoint security.oauth2.client.pre-established-redirect-uri --Redirect URL after authentication (* Callback URL set on LINE side) security.oauth2.client.scope --List of scopes that clients can use when requesting tokens security.oauth2.client.clientAuthenticationScheme --Format used for OAuth2 Token Endpoint call (* LINE supports FORM) security.oauth2.resource.userInfoUri --Endpoint provided for obtaining user information (user ID, user name, profile image)
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: [.p12 file path]
key-store-password: [password]
keyStoreType: PKCS12
keyAlias: MyAlias
logging:
level:
org.springframework.web: DEBUG
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;
}
}
--Register the variable information acquisition class set above in the 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();
}
}
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();
}
}
--After authentication is completed, obtain authorization code and execute access token issuance / user information acquisition request -Inject the LoginRestClient class with @Autowired --Pass the obtained object to Thymeleaf template with ModelAndView class
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;
}
}
--Request access token issuing API with authorization code as argument with getToken method --Get user information by giving an access token to the Authorization header with the getUserInfo method.
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;
}
}
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;}
}
--With the refresh token, the access token can be reissued by requesting the token even after the expiration date of the access token (30 days later). --The refresh token is valid for up to 10 days after the access token expires.
Since the ID Token is returned in JWT format, Base64 decode the header / payload part and refer to the contents.
header: {"typ":"JWT","alg":"HS256"}
payload: {"iss":"https://access.line.me","sub":"**********","aud":"1653797412","exp":1579957272,"iat":1579953672,"amr":["linesso"],"name":"Yu","picture":"https://profile.line-scdn.net/0ho4KGrFFFMBt2PhvCq9pPTEp7PnYBEDZTDg98LVpsanhTXn4aTFx5Lwdpbn9SWnEYHwgtfFs3Znhd"}
Claim name | Claim content |
---|---|
iss | Token issuer(ISSuer) |
sub | Token target(SUBubect) |
aud | Token recipient(AUDience) |
exp | Token expiration date(EXPiration time) |
iat | Token issuance time stamp(Issued-AT) |
Recommended Posts