Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GitHubでログイン/暗黙的サインアップをできるようにした #16

Merged
merged 6 commits into from Sep 27, 2018
@@ -30,7 +30,7 @@ dependencies {
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.flywaydb:flyway-core')
compile('org.seasar.doma:doma:+')
compile('org.seasar.doma.boot:doma-spring-boot-starter:+')

This comment has been minimized.

Copy link
@nokok

nokok Sep 20, 2018

Collaborator

非常に今更なんだけど、バージョンを指定していないのが非常に違和感がある。。。ビルドの一貫性再現性が保たれないような気がしているのでマージ前にちょっとコメントほしい

This comment has been minimized.

Copy link
@orekyuu

orekyuu Sep 20, 2018

Author Owner

spring-boot-starter系はバージョン固定されてるんだけど、domaとかはそうじゃないので固定したほうが良いのは直したほうが良さそう。
他の選択手段としてはGradle Dependency Lock Plugin使うという手もあるけど別PRかな。

compile('org.springframework.security:spring-security-oauth2-client:+')
compile('org.springframework.security:spring-security-oauth2-jose:+')
runtime('org.springframework.boot:spring-boot-devtools')
0 gradlew 100644 → 100755
No changes.
@@ -1,7 +1,8 @@
package net.tuzigiri.config;

import net.tuzigiri.domain.identity.AuthorizedClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
@@ -10,6 +11,11 @@
@Configuration
@EnableWebSecurity
public class TuzigiriSecurityConfig extends WebSecurityConfigurerAdapter {
final AuthorizedClientService authorizedClientService;

public TuzigiriSecurityConfig(AuthorizedClientService authorizedClientService) {
this.authorizedClientService = authorizedClientService;
}

@Override
public void configure(WebSecurity web) throws Exception {
@@ -21,6 +27,6 @@ protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login();
.oauth2Login().authorizedClientService(authorizedClientService);
}
}
@@ -0,0 +1,58 @@
package net.tuzigiri.domain.identity;

import org.seasar.doma.Column;
import org.seasar.doma.Embeddable;
import org.springframework.security.oauth2.core.OAuth2AccessToken;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;

@Embeddable
public class AccessToken {
@Column(name = "token_value")
private String tokenValue;
@Column(name = "issued_at")
private LocalDateTime issuedAt;
@Column(name = "expires_at")
private LocalDateTime expiresAt;

public AccessToken(String tokenValue, LocalDateTime issuedAt, LocalDateTime expiresAt) {
this.tokenValue = tokenValue;
this.issuedAt = issuedAt;
this.expiresAt = expiresAt;
}

public OAuth2AccessToken toAccessToken() {
ZoneOffset jstOffset = ZoneOffset.ofHours(9);
return new OAuth2AccessToken(
OAuth2AccessToken.TokenType.BEARER,
tokenValue, issuedAt.toInstant(jstOffset),
expiresAt.toInstant(jstOffset)
);
}

public String getTokenValue() {
return tokenValue;
}

public void setTokenValue(String tokenValue) {
this.tokenValue = tokenValue;
}

public LocalDateTime getIssuedAt() {
return issuedAt;
}

public void setIssuedAt(LocalDateTime issuedAt) {
this.issuedAt = issuedAt;
}

public LocalDateTime getExpiresAt() {
return expiresAt;
}

public void setExpiresAt(LocalDateTime expiresAt) {
this.expiresAt = expiresAt;
}
}
@@ -0,0 +1,44 @@
package net.tuzigiri.domain.identity;

import net.tuzigiri.util.Id;
import org.seasar.doma.*;

import java.util.Objects;

@Entity(immutable = true)
@Table(name = "accounts")
public class Account {
@org.seasar.doma.Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private final Id<Account> id;
@Column(name = "display_name")
private final String displayName;

public Account(Id<Account> id, String displayName) {
this.id = id;
this.displayName = displayName;
}

public Id<Account> getId() {
return id;
}

public String getDisplayName() {
return displayName;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Account account = (Account) o;
return Objects.equals(id, account.id);
}

@Override
public int hashCode() {

return Objects.hash(id);
}
}
@@ -0,0 +1,13 @@
package net.tuzigiri.domain.identity;

import org.seasar.doma.Dao;
import org.seasar.doma.Insert;
import org.seasar.doma.boot.ConfigAutowireable;
import org.seasar.doma.jdbc.Result;

@Dao
@ConfigAutowireable
public interface AccountDao {
@Insert
Result<Account> insert(Account account);
}
@@ -0,0 +1,19 @@
package net.tuzigiri.domain.identity;

import org.seasar.doma.Dao;
import org.seasar.doma.Insert;
import org.seasar.doma.Select;
import org.seasar.doma.boot.ConfigAutowireable;
import org.seasar.doma.jdbc.Result;

import java.util.Optional;

@Dao
@ConfigAutowireable
public interface AuhorizedClientDao {
@Insert
Result<AuthorizedClient> insert(AuthorizedClient client);

@Select
Optional<AuthorizedClient> findByAuhorizedClientId(AuthorizedClientId authorizedClientId);
}
@@ -0,0 +1,55 @@
package net.tuzigiri.domain.identity;

import net.tuzigiri.util.Id;
import org.seasar.doma.*;

import java.util.Objects;

@Entity(immutable = true)
@Table(name = "authorized_clients")
public class AuthorizedClient {
@Column(name = "id")
@org.seasar.doma.Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private final Id<AuthorizedClient> id;
@Column(name = "account_id")
private final Id<Account> accountId;
private final AuthorizedClientId authorizedClientId;
private final AccessToken accessToken;

public AuthorizedClient(Id<AuthorizedClient> id, Id<Account> accountId, AuthorizedClientId authorizedClientId, AccessToken accessToken) {
this.id = id;
this.accountId = accountId;
this.authorizedClientId = authorizedClientId;
this.accessToken = accessToken;
}

public Id<AuthorizedClient> getId() {
return id;
}

public Id<Account> getAccountId() {
return accountId;
}

public AuthorizedClientId getAuthorizedClientId() {
return authorizedClientId;
}

public AccessToken getAccessToken() {
return accessToken;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AuthorizedClient that = (AuthorizedClient) o;
return Objects.equals(id, that.id);
}

@Override
public int hashCode() {
return Objects.hash(id);
}
}
@@ -0,0 +1,49 @@
package net.tuzigiri.domain.identity;

import org.seasar.doma.Column;
import org.seasar.doma.Embeddable;

import java.util.Objects;

@Embeddable
public class AuthorizedClientId {
@Column(name = "principal_name")
private String principalName;
@Column(name = "registration_id")
private String registrationId;

public AuthorizedClientId(String principalName, String registrationId) {
this.principalName = principalName;
this.registrationId = registrationId;
}

public String getPrincipalName() {
return principalName;
}

public void setPrincipalName(String principalName) {
this.principalName = principalName;
}

public String getRegistrationId() {
return registrationId;
}

public void setRegistrationId(String registrationId) {
this.registrationId = registrationId;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AuthorizedClientId that = (AuthorizedClientId) o;
return Objects.equals(principalName, that.principalName) &&
Objects.equals(registrationId, that.registrationId);
}

@Override
public int hashCode() {
return Objects.hash(principalName, registrationId);
}
}
@@ -0,0 +1,61 @@
package net.tuzigiri.domain.identity;

import net.tuzigiri.util.Id;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.stereotype.Component;

import java.time.ZoneOffset;

@Component
public class AuthorizedClientService implements OAuth2AuthorizedClientService {
private final AuhorizedClientDao auhorizedClientDao;
private final SignupService signupService;

public AuthorizedClientService(AuhorizedClientDao auhorizedClientDao, SignupService signupService) {
this.auhorizedClientDao = auhorizedClientDao;
this.signupService = signupService;
}


@Override
@SuppressWarnings("unchecked")
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId, String principalName) {
OAuth2AuthorizedClient client = auhorizedClientDao.findByAuhorizedClientId(new AuthorizedClientId(principalName, clientRegistrationId))
.map(it -> {
ClientRegistration clientRegistration = ClientRegistration.withRegistrationId(clientRegistrationId).build();
AuthorizedClientId clientId = it.getAuthorizedClientId();
OAuth2AccessToken oAuth2AccessToken = it.getAccessToken().toAccessToken();
return new OAuth2AuthorizedClient(clientRegistration, clientId.getPrincipalName(), oAuth2AccessToken);
}).orElse(null);
return (T) client;
}

@Override
public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal) {
String principalName = authorizedClient.getPrincipalName();
String registrationId = authorizedClient.getClientRegistration().getRegistrationId();
OAuth2AccessToken oAuth2AccessToken = authorizedClient.getAccessToken();

ZoneOffset jstOffset = ZoneOffset.ofHours(9);
AccessToken accessToken = new AccessToken(
oAuth2AccessToken.getTokenValue(),
oAuth2AccessToken.getIssuedAt().atOffset(jstOffset).toLocalDateTime(),
oAuth2AccessToken.getIssuedAt().atOffset(jstOffset).toLocalDateTime()
);

//FIXME: 名前を取る方法が辛い。
String name = (String) ((DefaultOAuth2User) principal.getPrincipal()).getAttributes().get("name");

This comment has been minimized.

Copy link
@orekyuu

orekyuu Sep 20, 2018

Author Owner

GitHub前提のコード。辛い。

This comment has been minimized.

Copy link
@nokok

nokok Sep 20, 2018

Collaborator

YAGNIの観点で別にそれでも良いと思う

Account account = new Account(Id.notAssigned(), name);
signupService.signup(account, accessToken, new AuthorizedClientId(principalName, registrationId));
}

@Override
public void removeAuthorizedClient(String clientRegistrationId, String principalName) {

This comment has been minimized.

Copy link
@orekyuu

orekyuu Sep 20, 2018

Author Owner

削除作ってないけどこのPRでやるべきかな。別PRでもいいかなって思ったりもしてる

This comment has been minimized.

Copy link
@nokok

nokok Sep 20, 2018

Collaborator

別PRでも良いかなと思う


}
}
@@ -0,0 +1,30 @@
package net.tuzigiri.domain.identity;

import net.tuzigiri.util.Id;
import org.seasar.doma.jdbc.Result;
import org.seasar.doma.jdbc.UniqueConstraintException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class SignupService {
private final AccountDao accountDao;
private final AuhorizedClientDao auhorizedClientDao;

public SignupService(AccountDao accountDao, AuhorizedClientDao auhorizedClientDao) {
this.accountDao = accountDao;
this.auhorizedClientDao = auhorizedClientDao;
}

public void signup(Account account, AccessToken accessToken, AuthorizedClientId clientId) {
try {
Result<Account> accountResult = accountDao.insert(account);
AuthorizedClient client = new AuthorizedClient(Id.notAssigned(), accountResult.getEntity().getId(), clientId, accessToken);
auhorizedClientDao.insert(client);
} catch (UniqueConstraintException | DuplicateKeyException e) {
// すでに登録済みなら無視
}
}
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.