尝试使用Spring Boot Security

环境

Spring Boot 1.5.9.RELEASE(发布版)
Java 8
Maven 4.0.0

总结

使用Spring Security进行身份验证,将登录信息与数据库进行比对。
数据库使用H2DB,ORM使用Doma。

首先

spring-initializr.png
ファクトリー・パス.png

代码 (daima)

只列出已从默认值更改过的内容。

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</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.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.seasar.doma.boot/doma-spring-boot-starter -->
        <dependency>
            <groupId>org.seasar.doma.boot</groupId>
            <artifactId>doma-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>
    </dependencies>
package com.example.springbootsecuritysample.entity;

import java.util.Collection;

import org.seasar.doma.Entity;
import org.seasar.doma.Id;
import org.seasar.doma.jdbc.entity.NamingType;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import lombok.Getter;
import lombok.Setter;

/**
 * USERテーブルのEntity
 * @author T.Harao
 *
 */
@Entity(naming = NamingType.SNAKE_UPPER_CASE)
@Getter
@Setter
public class UserEntity implements UserDetails {

    @Id
    private String userId;
    private String password;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }
    @Override
    public String getUsername() {
        return userId;
    }
    /**
     * UserDetailsServiceでチェックするパスワードを返却する
     * Lombokを使用している場合、フィールドに「password」があれば、
     * @GetterでgetPassword()を生成してくれる為、不要
     */
    @Override
    public String getPassword() {
        return password;
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return true;
    }

}
package com.example.springbootsecuritysample.dao;

import org.seasar.doma.Dao;
import org.seasar.doma.Select;
import org.seasar.doma.boot.ConfigAutowireable;

import com.example.springbootsecuritysample.entity.UserEntity;

/**
 * USERテーブルにアクセスするDAO
 * @author T.Harao
 *
 */
@Dao
@ConfigAutowireable
public interface UserDao {

    @Select
    public UserEntity selectByUserId(String userId);

}
package com.example.springbootsecuritysample.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.example.springbootsecuritysample.dao.UserDao;
import com.example.springbootsecuritysample.entity.UserEntity;

/**
 * 認証を扱うService
 * @author T.Harao
 *
 */
@Service
public class AuthService implements UserDetailsService {

    @Autowired
    private UserDao dao;

    /**
     * ユーザー読み込み
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        if(username == null || "".equals(username)) {
            throw new UsernameNotFoundException("ユーザーIDが未入力です");
        }

        UserEntity user = dao.selectByUserId(username);
        if(user == null) {
            throw new UsernameNotFoundException("ユーザーIDが不正です。");
        }

        return user;
    }

}
package com.example.springbootsecuritysample.form;

import org.hibernate.validator.constraints.NotEmpty;

import lombok.Getter;
import lombok.Setter;

/**
 * IndexControllerで使用するForm
 * @author T.Harao
 *
 */
@Getter
@Setter
public class IndexForm {

    @NotEmpty
    private String userId;
    @NotEmpty
    private String password;

}
package com.example.springbootsecuritysample.controller;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import com.example.springbootsecuritysample.form.IndexForm;

/**
 * ログイン
 * @author T.Harao
 *
 */
@Controller
@RequestMapping({"/", "/index"})
public class IndexController {

    @ModelAttribute
    public IndexForm initForm(){
        return new IndexForm();
    }

    /**
     * 初期表示
     * @param mv
     * @return
     */
    @RequestMapping(value = {"/", "/index"}, method = RequestMethod.GET)
    public ModelAndView index(ModelAndView mv) {
        mv.setViewName("index/index");
        return mv;
    }

    /**
     * 認証エラー時
     * @param mv
     * @return
     */
    @RequestMapping(value = {"/", "/index"}, method = RequestMethod.POST)
    public ModelAndView login(@ModelAttribute @Validated IndexForm form, BindingResult result, ModelAndView mv) {

        if(!result.hasErrors()) {
            mv.addObject("errorMessage", "ログイン情報が間違っています");
        }

        mv.setViewName("index/index");
        return mv;
    }

}
package com.example.springbootsecuritysample.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

/**
 * メニュー
 * @author T.Harao
 *
 */
@Controller
@RequestMapping("/menu")
public class MenuController {

    /**
     * 初期表示
     * @param mv
     * @return
     */
    @RequestMapping(value = {"/", "/index"}, method = RequestMethod.GET)
    public ModelAndView index(ModelAndView mv) {
        mv.setViewName("menu/index");
        return mv;
    }

}
package com.example.springbootsecuritysample.config.handler;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

/**
 * 認証失敗時のハンドラ
 * @author T.Harao
 *
 */
@Component
public class FailureHandler implements AuthenticationFailureHandler {

    /**
     * 認証失敗時
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {

        //「/」にForwardする
        RequestDispatcher dispatch = request.getRequestDispatcher("/");
        dispatch.forward(request, response);

    }

}
package com.example.springbootsecuritysample.config.handler;

import java.io.IOException;

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

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
/**
 * 認証成功時のハンドラ
 * @author T.Harao
 *
 */
@Component
public class SuccessHandler implements AuthenticationSuccessHandler {

    /**
     * 認証成功時
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {

        //「/menu/」にリダイレクトする
        response.sendRedirect(request.getContextPath() + "/menu/");

    }

}
package com.example.springbootsecuritysample.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.security.crypto.password.NoOpPasswordEncoder;

import com.example.springbootsecuritysample.config.handler.FailureHandler;
import com.example.springbootsecuritysample.config.handler.SuccessHandler;
import com.example.springbootsecuritysample.service.AuthService;

/**
 * セキュリティ設定
 * @author T.Harao
 *
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthService service;

    @Autowired
    private FailureHandler failureHandler;

    @Autowired
    private SuccessHandler successHandler;

    /**
     * WebSecurityの設定
     */
    @Override
    public void configure(WebSecurity web) throws Exception {

        // 静的リソース(images、css、javascript)とH2DBのコンソールに対するアクセスはセキュリティ設定を無視する
        web.ignoring().antMatchers("/css/**", "/fonts/**", "/images/**", "/js/**", "/h2-console/**");

    }

    /**
     * HttpSecurityの設定
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //認可の設定
        http.authorizeRequests()
            //認証無しでアクセスできるURLを設定
            .antMatchers("/", "/index/**").permitAll()
            //上記以外は認証が必要にする設定
            .anyRequest().authenticated();

        //ログイン設定
        http.formLogin()
            //認証処理のパスを設定
            .loginProcessingUrl("/index/login")
            //ログインフォームのパスを設定
            .loginPage("/")
            .loginPage("/index/**")
            //認証成功時にリダイレクトするURLを設定
            .defaultSuccessUrl("/menu/")
            //認証失敗時にforwardするURLを設定
            .failureForwardUrl("/")
            //認証成功時にforwardするURLを設定
            //.successForwardUrl("/")
            //認証成功時に呼ばれるハンドラクラスを設定
            //.successHandler(successHandler)
            //認証失敗時にリダイレクトするURLを設定
            //.failureUrl("/menu/")
            //認証失敗時に呼ばれるハンドラクラスを設定
            //.failureHandler(failureHandler)
            //ユーザー名、パスワードのパラメータ名を設定
            .usernameParameter("userId").passwordParameter("password");

    }

    /**
     * 設定
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        //パスワードは平文でDBに登録する為、「NoOpPasswordEncoder」を設定する
        auth.userDetailsService(service)
            .passwordEncoder(NoOpPasswordEncoder.getInstance());

    }


}
<!DOCTYPE html>
<html
    xmlns        = "http://www.w3.org/1999/xhtml"
    xmlns:th     = "http://www.thymeleaf.org"
    xmlns:layout = "http://www.ultraq.net.nz/thymeleaf/layout"
>
<head>
    <meta charset="UTF-8" />
    <title layout:title-pattern="$DECORATOR_TITLE - $CONTENT_TITLE">Spring Securityテスト</title>
    <link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}" media="all" />
    <link rel="stylesheet" type="text/css" href="/css/bootstrap-theme.min.css" th:href="@{/css/bootstrap-theme.min.css}" media="all" />

    <script type="text/javascript" src="/js/jquery-1.12.4.min.js" th:src="@{/js/jquery-1.12.4.min.js}"></script>
    <script type="text/javascript" src="/js/bootstrap.min.js" th:src="@{/js/bootstrap.min.js}"></script>
</head>
<body>
    <div class="contents" layout:fragment="contents"></div>
</body>
</html>
<!DOCTYPE html>
<html
    xmlns        = "http://www.w3.org/1999/xhtml"
    xmlns:th     = "http://www.thymeleaf.org"
    xmlns:layout = "http://www.ultraq.net.nz/thymeleaf/layout"
    layout:decorator="layout"
    >
<head>
    <title>ログイン</title>
</head>
<body>
    <div layout:fragment="contents">
        <form class="form-horizontal" method="POST" action="/index/login/" th:action="@{/index/login}" th:object="${indexForm}">
            <div th:text="${errorMessage}?: ''" class="col-sm-offset-2 text-danger"></div>
            <div class="form-group">
                <p th:if="${#fields.hasErrors('*{userId}')}" th:errors="*{userId}" class="col-sm-offset-2 text-danger"></p>
                <label for="user-id" class="col-sm-2 control-label">ユーザーID</label>
                <div class="col-sm-5">
                    <input type="text" class="form-control" id="user-id" th:field="*{userId}" placeholder="ユーザーID" />
                </div>
            </div>
            <div class="form-group">
                <p th:if="${#fields.hasErrors('*{password}')}" th:errors="*{password}" class="col-sm-offset-2 text-danger"></p>
                <label for="password" class="col-sm-2 control-label">パスワード</label>
                <div class="col-sm-5">
                    <input type="password" class="form-control" id="password" th:field="*{password}" placeholder="パスワード" />
                </div>
            </div>
            <div class="form-group">
                <input type="submit" class="btn btn-primary col-sm-2 col-sm-offset-2" name="login" value="ログイン" />
                <input type="reset" class="btn btn-default col-sm-2 col-sm-offset-1" name="clear" value="クリア" />
            </div>
        </form>
    </div>
</body>
</html>
<!DOCTYPE html>
<html
    xmlns        = "http://www.w3.org/1999/xhtml"
    xmlns:th     = "http://www.thymeleaf.org"
    xmlns:layout = "http://www.ultraq.net.nz/thymeleaf/layout"
    layout:decorator="layout"
    >
<head>
    <title>メニュー</title>
</head>
<body>
    <div layout:fragment="contents">
        <h2>メニュー</h2>
    </div>
</body>
</html>
#spring
spring:
  profiles:
    active: dev
  datasource:
    url: jdbc:h2:./db

#server
server:
  contextPath: /security-sample

#doma
doma:
  dialect: h2
#spring
spring:
  h2:
    console:
      enabled: true
  thymeleaf:
    cache: false
#spring
spring:
  h2:
    console:
      enabled: false
  thymeleaf:
    cache: true
select
    user_id
    ,password
from
    user
where
    user_id = /*userId*/''
--drop table if exists user;
create table if not exists user (
    user_id     varchar(30) not null    primary key
    ,password   varchar(30) not null
);
insert into user (user_id,password) values ('test','pass');
パッケージエクスプローラー.PNG

确认行动

メニュー.PNG

关于认证后的处理

当认证成功或失败时,处理的相关代码将被写在SecurityConfig.java文件的以下部分中。

    /**
     * HttpSecurityの設定
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //認可の設定
        http.authorizeRequests()
            //認証無しでアクセスできるURLを設定
            .antMatchers("/", "/index/**").permitAll()
            //上記以外は認証が必要にする設定
            .anyRequest().authenticated();

        //ログイン設定
        http.formLogin()
            //認証処理のパスを設定
            .loginProcessingUrl("/index/login")
            //ログインフォームのパスを設定
            .loginPage("/")
            .loginPage("/index/**")
            //認証成功時にリダイレクトするURLを設定
            .defaultSuccessUrl("/menu/")
            //認証失敗時にforwardするURLを設定
            .failureForwardUrl("/")
            //認証成功時にforwardするURLを設定
            //.successForwardUrl("/")
            //認証成功時に呼ばれるハンドラクラスを設定
            //.successHandler(successHandler)
            //認証失敗時にリダイレクトするURLを設定
            //.failureUrl("/menu/")
            //認証失敗時に呼ばれるハンドラクラスを設定
            //.failureHandler(failureHandler)
            //ユーザー名、パスワードのパラメータ名を設定
            .usernameParameter("userId").passwordParameter("password");

    }

虽然这里进行了注释,但在认证成功或失败时,都可以将处理委托给重定向、转发或处理程序类。

我们刚刚完成的项目在这里。

bannerAds