くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Springの監査情報で、だれがいつ作成or更新したかを自動で付与する(SpringBoot/SpringData JPA)

DBの情報で、作成者や作成日や最終更新者や最終更新日を自分で追加していたけど、 Springに該当の機能があったので、調べたときの備忘録

やることは簡単、2ステップ。

1. Configクラスを作る

@Configuration
@EnableJpaAuditing
public class JpaAuditorAwareConfig {

    @Bean
    public AuditorAware<UserEntity> auditorAware() {
        return new SpringSecurityAuditor();
    }

    public static class SpringSecurityAuditor implements
            AuditorAware<UserEntity> {

        @Override
        public UserEntity getCurrentAuditor() {
            SecurityContext context = SecurityContextHolder.getContext();
            Authentication authentication = context.getAuthentication();
            if (authentication == null || !authentication.isAuthenticated()) {
                return null;
            }

            return (UserEntity) authentication.getPrincipal();
        }

    }
}

2. Entityに@CreateDateとかをつける

@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table
public class FooEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @CreatedDate
    @Column(nullable = false)
    private LocalTime createdDate;  // 作成日

    @LastModifiedDate
    @Column(nullable = false)
    private LocalTime modifiedDate;  // 最終更新日

    @CreatedBy
    @OneToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "created_user_id")
    private UserEntity createdBy;   // 作成者
    
    @LastModifiedBy
    @OneToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "modifiyed_user_id")
    private UserEntity modifiedBy;  // 最終更新者
}

以上!!

参考にしたサイト様

Spring Securityのカスタマイズ、どこでなにするのか?のおぼえがき

Spring Boot/Spring Securityを使って、認証部分のカスタマイズについていろいろ調べてみた、ときの備忘録
というか、やってみたこと、というか、おぼえがき

認証全体の流れ

リクエストが来たら

  1. SecurityFilter/AuthenticationFilterを通って
  2. AuthenticationManagerがAuthenticationProviderを呼び出し、
  3. AuthenticationProviderがUserDetailsSerivceを呼び出し、
  4. UserDetailsSerivceが、UserDetailsを返す。
  5. 認証が成功すると、AuthenticationFilterが、AuthenticationSuccessHandlerを呼び出す
  6. 認証が失敗すると、AuthenticationFilterが、AuthenticationFailureHandlerを呼び出す

多いし複雑。。さらに、認証が行われると認証イベントが発生し、それもハンドリングできる。

データベース認証をカスタマイズしたい(UserDetailsSerivce+UserDetails)

以下の2つを用意すればOK

  • UserDetailsを実装した資格情報を格納するクラス
  • UserDetailsSerivceを実装したDBからUserDetailsを取得するクラス

前の記事でも書いているので省略。

wannabe-jellyfish.hatenablog.com

認証処理の前にリクエストをフィルタしたり、追加情報を使いたい(AuthenticationFilter)

  • 認証処理に行く前に何かしたい場合は、AuthenticationFilterの実装をする
  • form認証の場合、デフォルトではUsernamePasswordAuthenticationFilterが使われる
  • UsernamePasswordAuthenticationFilterを拡張してゴニョゴニョするのがよい
public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
     if (!request.getMethod().equals("POST")) {
   throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
  }
       String username = obtainUsername(request);
       String password = obtainPassword(request);
       // このあたりでゴニョゴニョする。
        
       UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

        // Allow subclasses to set the "details" property
       setDetails(request, authRequest);
       
       return this.getAuthenticationManager().authenticate(authRequest);
    }
}
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // Java Configではこんな感じ
        http.addFilterAt(new MyUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

認証成功時の処理をカスタマイズしたい(AuthenticationSuccessHandler)

  • AuthenticationSuccessHandlerを実装したクラスを使う
  • デフォルトはForwardAuthenticationSuccessHandler?SavedRequestAwareAuthenticationSuccessHandler?のよう
  • ForwardAuthenticationSuccessHandlerの実装はこんな感じ。authenticationを見て、遷移先とかをゴニョゴニョできる
public class ForwardAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private final String forwardUrl;

    /**
    * @param forwardUrl
    */
    public ForwardAuthenticationSuccessHandler(String forwardUrl) {
        Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl), "'" + forwardUrl + "' is not a valid forward URL");
        this.forwardUrl = forwardUrl;
    }

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        request.getRequestDispatcher(forwardUrl).forward(request, response);
    }
}

認証失敗時の処理をカスタマイズしたい(AuthenticationFailureHandler)

  • AuthenticationSuccessHandlerを実装したクラスを使う
  • デフォルトはForwardAuthenticationFailureHandler?SimpleUrlAuthenticationFailureHandler?のよう
  • ForwardAuthenticationFailureHandlerの実装はこんな感じ。exceptionを見て、遷移先とかをゴニョゴニョできる
public class ForwardAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private final String forwardUrl;

    /**
    * @param forwardUrl
    */
    public ForwardAuthenticationFailureHandler(String forwardUrl) {
        Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl), "'" + forwardUrl + "' is not a valid forward URL");
        this.forwardUrl = forwardUrl;
    }

    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
        request.getRequestDispatcher(forwardUrl).forward(request, response);
    }
}

参考にしたサイト様

supervisorでデーモン化する

Djangoのbackground tasksをデーモン化したいなーと思ったので、その際の備忘録。

supervisorとは

インストール

$ sudo yum install supervisor
$ sudo pip install supervisor

yumでインストールするとpython26も一緒にインストールされる。
python27をインストールしていたので、yumだけだと動かなかった。。 なので、pipを使って、supervisorをインストールした。

インストールするとそれぞれ以下の場所にインストールされる

  • 起動用のスクリプト ... /etc/init.d/supervisord
  • コンフィグファイル ... /etc/supervisord.conf
  • コンフィグ用ディレクトリ ... /etc/supervisord.d ※includeするコンフィグの置き場
  • 本体のログファイル ... /var/log/supervisor/supervisord.log

起動・停止

yumでインストールするとserviceコマンドで管理できる

# 起動
$ sudo service supervisord start
# 停止
$ sudo service supervisord stop

設定方法

supervisord.confに以下のようなテンプレートが用意されているので、
適宜コメントインして、設定すればOK。

;[program:theprogramname]
;directory=/tmp              ; (default is not to cd during start)
;command=/bin/cat            ; the program (relative uses PATH, can take args)
;priority=999                ; the relative start priority (default 999)
;autostart=true              ; start at supervisord start (default: true)
;autorestart=true            ; retstart at unexpected quit (default: true)
;startsecs=10                ; number of secs prog must stay running (def. 10)
;startretries=3              ; max # of serial start failures (default 3)
;exitcodes=0,2               ; 'expected' exit codes for process (default 0,2)
;stopsignal=QUIT             ; signal used to kill process (default TERM)
;stopwaitsecs=10             ; max num secs to wait before SIGKILL (default 10)
;user=chrism                 ; setuid to this UNIX account to run the program
;log_stdout=true             ; if true, log program stdout (default true)
;log_stderr=true             ; if true, log program stderr (def false)
;logfile=/var/log/cat.log    ; child log path, use NONE for none; default AUTO
;logfile_maxbytes=1MB        ; max # logfile bytes b4 rotation (default 50MB)
;logfile_backups=10          ; # of logfile backups (default 10)

例としては以下の感じ

[program:ProcessTasks]
directory=/var/www/cgi-bin/django
command=python manage.py process_tasks
autostart=true
autorestart=true
user=root
logfile=/var/log/process_tasks.log

その他のデーモン化ツール

  1. deamonize tool ... http://cr.yp.to/daemontools.html
  2. systemd

参考にしたサイト様

JavaでURLからファイルをダウンロードする(commons-io)

URLのファイルをダウンロードしたいなと思ったけど、
commonsさんで一発だった。ので、その備忘録。

URL url = new URL("http://example.com/foo.txt"); // ダウンロードしたいURL
File destFile = new File("bar.txt")                                   // 保存先
FileUtils.copyURLToFile(url, File)

参考にしたサイト様

Djangoでログをカスタマイズして、レスポンス時の詳細をロギングする

Djangoを利用している際に、標準のロギングだとリクエスト時のパラメタが表示されない。。
POSTだとURLしか確認できないため、拡張してみたときの備忘録。

リクエスト/レスポンスに関する拡張ポイント

Djangoでは、色々と拡張ポイントが用意されているらしく、
Middlewareを作ってその拡張ポイントを利用することができる。

今回調べたのはこんな感じ。ほかにもprocess_template_responseとかprocess_viewとかあるらしい。

# -*- coding: utf-8 -*
class LoggingRequestMiddleware(object):

  """ リクエスト時のハンドリング """
  def process_request(self, request):
  
  """ リクエスト時に例外発生時のハンドリング """
  def process_exception(self, request, exception)
  
  """ レスポンス返却時のハンドリング """
  def process_response(self, request, response):
    return response

レスポンス時のステータスとリクエスト詳細をログに表示する

ログを出力するMiddlewareを作成する

process_responseを拡張して、パラメタやログインユーザも表示する用に変更してみた。

# -*- coding: utf-8 -*
import logging
import traceback

class LoggingRequestMiddleware(object):
    logger = logging.getLogger('django.request')

    def process_response(self, request, response):
        try:
            request_info = self.__get_request_info(request)
            user_info = self.__get_user_info__(request.user)

            msg = u"{} {} \t{}".format(response.status_code, request_info, user_info)
            self.logger.info(msg)
        except:
            self.logger.error(traceback.format_exc())

        return response

    def __get_request_info(self, request):
        if request.method == u'GET':
            params = self.__format_params__(dict(request.GET))
        elif request.method == u'POST':
            params = self.__format_params__(dict(request.POST))
        else:
            params = u''

        return u'{} {}\tparam:[{}]'.format(request.method, request.get_full_path(), params)

    def __get_user_info__(self, user):
        if user is None:
            return u'user: - '

        try:
            return u'user: {}({})'.format(user, user.id)
        except:
            return u'user: - '

    def __format_params__(self, params):
        param_items = filter(lambda (k, v):k != u'csrfmiddlewaretoken',params.items())
        return u', '.join([u'{}={}'.format(key, self.__list_str__(value)) for (key, value) in param_items])

    def __list_str__(self, params):
        if params is None:
            return u''
        elif len(params) == 1:
            return u'{}'.format(params[0])
            
        return u'[' + u', '.join(u'{}'.format(item) for item in params) + u']'

作成したMiddlewareを利用するようにsettings.pyを修正する

作ったMiddlewareを有効にするには、MIDDLEWARE_CLASSESに追加すればOK!

MIDDLEWARE_CLASSES = [
   ・・・
    'app.middle.LoggingRequestMiddleware'
]

以上!!

参考にしたサイト様

Androidのエミュレータでネットワークを使えるようにする

エミュレータを動かしているPCにGoogleDNSを登録する必要があるらしい。

GoogleのDSNは、以下の2つ

  • 8.8.8.8
  • 8.8.4.4

参考にしたサイト様

Androidで電話の発信・切断・状態の確認

Androidで電話を操作するアプリを作る機会があったので、その時の備忘録。

発信する

  • 発信する場合は簡単。Intentを発行するだけでOK。
Context context = ...;
String phoneNumber = "090-0000-9999"
Uri uri = Uri.parse("tel:" + phoneNumber);
Intent intent = new Intent(Intent.ACTION_CALL, uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // Activity以外から呼ぶ場合に必要
context.startActivity(intent);

切断する

  • 切断する場合は、APIが公開されていないため、複雑
  • 非公開APIなので、今後使えなくなる可能性もある
  • また、電話を扱う場合は、パーミッションも必要
<uses-permission android:name="android.permission.CALL_PHONE"/>

1. 非公開インターフェースITelephonyを自前で用意する

package com.android.internal.telephony;

public interface ITelephony {
    boolean endCall(); // 切断
    void answerRingingCall(); // 応答
    void silenceRinger(); // 着信音を消す
}

2. リフレクションを使って、ITelephonyのインスタンスを取得

// TelephonyManagerを取得
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);

// protectedなTelephonyManager.getITelephony()のアクセス修飾子を変更して、呼び出す
Class<?> c = Class.forName(telephonyManager.getClass().getName());
Method m = c.getDeclaredMethod("getITelephony");
m.setAccessible(true);
ITelephony telephony = (ITelephony) m.invoke(telephonyManager);

3. 取得したITelephonyのインスタンスで切断

telephony.endCall(); // 切断

TelephonyManagerまではソースが見れるので、そこからいろいろ見てみると良いかも。

電話の状態を確認する

  • PhoneStateListenerが用意されているので、extendする
  • 電話番号も取得したい場合には、パーミッションが必要
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
  • 電話の状態を確認するには、PhoneStateListenerのonCallStateChangedをOverrideする
// TelephonyManagerを取得
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);

// listenerを登録
telephonyManager.listen(new PhoneStateListener(){
    // PhoneStateListenerの`onCallStateChanged`をOverride
    @Override
    public void onCallStateChanged(int state, String incomingNumber) {
        // パーミッションがない場合、incomingNumberは常に空
        switch (state) {
          case TelephonyManager.CALL_STATE_RINGING:
            /* 着信 */
            Log.i(TAG, "Call State Changed: CALL_STATE_RINGING");
            break;
        case TelephonyManager.CALL_STATE_OFFHOOK:
            /* 通話 */
            Log.i(TAG, "Call State Changed: CALL_STATE_OFFHOOK");
            break;
        case TelephonyManager.CALL_STATE_IDLE:
            /* 待受 */
            Log.i(TAG, "Call State Changed: CALL_STATE_IDLE");
            break;
        }
    }
}, PhoneStateListener.LISTEN_CALL_STATE);

以上!!

参考にしたサイト様