diff --git a/class.png b/class.png index d9c159a..c1df006 100644 --- a/class.png +++ b/class.png Binary files differ diff --git a/class.pu b/class.pu index b239eb7..f8e5a03 100644 --- a/class.pu +++ b/class.pu @@ -9,20 +9,47 @@ lat : Double } } -note bottom of db : spring.datasource.url=jdbc:h2:mem:taskdb\nspring.jpa.hibernate.ddl-auto=none +note bottom of db : spring.datasource.url=jdbc:h2:./taskdb\nspring.jpa.hibernate.ddl-auto=update class SecurityConfig <> { + - userDetailsService : UserDetailsService + passwordEncoder() : PasswordEncoder + configure(WebSecurity) configure(HttpSecurity) configure(AuthenticationManagerBuilder) } +class UserDetailsServiceImpl <> { + - userRepository : SiteUserRepository + + loadUserByUsername(username) : UserDetails + + createUserDetails(:SiteUser) : User +} + class SecurityController <> { + - userRepository : userRepository + - passwordEncoder : PasswordEncoder + success() + showList(loginUser, model) } +class SiteUser <> { + + id : Long + - username : String + - email : String@Email + - admin : boolean + - role : String + - active : boolean +} + +interface SiteUserRepository <> { + findByUsername(String) + existsByUsername(String) +} +SecurityConfig *-- UserDetailsServiceImpl +UserDetailsServiceImpl *-- SiteUserRepository +SecurityController *-- SiteUserRepository +SiteUserRepository <|-- SiteUser + class City <> { + id : Long - citycode : String diff --git a/controller.png b/controller.png index 65f849e..cf25323 100644 --- a/controller.png +++ b/controller.png Binary files differ diff --git a/controller.pu b/controller.pu index e214122..0ff02c3 100644 --- a/controller.pu +++ b/controller.pu @@ -4,19 +4,46 @@ state sidebar { state "ユーザ管理" as users - state "ログイン" as login + state "ユーザ一覧\n[Role=admin]" as list_ + state "ログイン" as login_ state "ログアウト" as logout state "BLDGデータリスト" as indeies state "BLDGデータ登録" as add + logout --> logout : /logout } -state "Sign in" as signin { - signin : Username - signin : Password +state "login" as login { + login : Username + login : Password } -login --> signin : /login -signin --> signin : [!success] -sidebar <-- signin : [success] + +state "register" as register { + register : username + register : password + register : email + register : admin +} + +login_ --> login : /login +login --> login : [!success] +sidebar <-- login : [success] +login --> register : /register 新規登録 + +state user { + user : username + user : role +} +users --> user : /user + +state "ユーザ一覧" as list { + state ユーザ { + ユーザ : id + ユーザ : username + ユーザ : email + ユーザ : role + } +} +list_ --> list : /admin/list state cities cities : *{citycode} @@ -33,12 +60,6 @@ } } -state user { - user : username - user : role -} -users --> user : /user - cities --> cities : /city/delete/{citycode} cities --> form : /city/edit/{citycode} add --> form : /city/add diff --git a/src/main/java/osm/surveyor/task/user/config/SecurityConfig.java b/src/main/java/osm/surveyor/task/user/config/SecurityConfig.java index 0b9e181..07d5f89 100644 --- a/src/main/java/osm/surveyor/task/user/config/SecurityConfig.java +++ b/src/main/java/osm/surveyor/task/user/config/SecurityConfig.java @@ -7,13 +7,19 @@ 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.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import lombok.RequiredArgsConstructor; +import osm.surveyor.task.util.Role; + +@RequiredArgsConstructor @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { + private final UserDetailsService userDetailsService; @Bean public PasswordEncoder passwordEncoder() { @@ -24,14 +30,15 @@ public void configure(WebSecurity web) throws Exception { // セキュリティ設定を、無視(ignoring)するパスを指定します // 通常、cssやjs、imgなどの静的リソースを指定します - web.ignoring().antMatchers("/city/**", "/css/**", "/img/**", "/webjars/**"); + web.ignoring().antMatchers("/city/**", "/js/**", "/css/**", "/img/**", "/webjars/**"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // 「/login」と「/error」をアクセス可能にします - .antMatchers("/login", "/error").permitAll() + .antMatchers("/login", "/error", "/register").permitAll() + .antMatchers("/admin/**").hasRole(Role.ADMIN.name()) .anyRequest().authenticated() .and() .formLogin() @@ -57,13 +64,6 @@ */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.inMemoryAuthentication() - .withUser("admin") - .password(passwordEncoder().encode("password")) - .authorities("ROLE_ADMIN") - .and() - .withUser("user") - .password(passwordEncoder().encode("password")) - .authorities("ROLE_USER"); + auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } } diff --git a/src/main/java/osm/surveyor/task/user/controller/SecurityController.java b/src/main/java/osm/surveyor/task/user/controller/SecurityController.java index adfa9de..f6ac827 100644 --- a/src/main/java/osm/surveyor/task/user/controller/SecurityController.java +++ b/src/main/java/osm/surveyor/task/user/controller/SecurityController.java @@ -1,13 +1,27 @@ package osm.surveyor.task.user.controller; import org.springframework.security.core.Authentication; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import lombok.RequiredArgsConstructor; +import osm.surveyor.task.user.model.SiteUser; +import osm.surveyor.task.user.repository.SiteUserRepository; +import osm.surveyor.task.util.Role; + +@RequiredArgsConstructor @Controller public class SecurityController { + private final SiteUserRepository userRepository; + private final PasswordEncoder passwordEncoder; + @GetMapping("/login") public String success() { return "login"; @@ -25,4 +39,34 @@ } return "user"; } + + @GetMapping("/admin/list") + public String showAdminList(Model model) { + model.addAttribute("users", userRepository.findAll()); + return "list"; + } + + @GetMapping("/register") + public String register(@ModelAttribute("user") SiteUser user) { + return "register"; + } + + @PostMapping("/register") + public String process(@Validated @ModelAttribute("user") SiteUser user, + BindingResult result) { + + if (result.hasErrors()) { + return "register"; + } + + user.setPassword(passwordEncoder.encode(user.getPassword())); + if (user.isAdmin()) { + user.setRole(Role.ADMIN.name()); + } else { + user.setRole(Role.USER.name()); + } + userRepository.save(user); + + return "redirect:/login?register"; + } } diff --git a/src/main/java/osm/surveyor/task/user/model/SiteUser.java b/src/main/java/osm/surveyor/task/user/model/SiteUser.java new file mode 100644 index 0000000..513cb2c --- /dev/null +++ b/src/main/java/osm/surveyor/task/user/model/SiteUser.java @@ -0,0 +1,35 @@ +package osm.surveyor.task.user.model; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Entity +public class SiteUser { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Size(min = 2, max = 20) + private String username; + + @Size(min = 4, max = 255) + private String password; + + @NotBlank + @Email + private String email; + + private boolean admin; + private String role; + private boolean active = true; +} diff --git a/src/main/java/osm/surveyor/task/user/repository/SiteUserRepository.java b/src/main/java/osm/surveyor/task/user/repository/SiteUserRepository.java new file mode 100644 index 0000000..83ee8b4 --- /dev/null +++ b/src/main/java/osm/surveyor/task/user/repository/SiteUserRepository.java @@ -0,0 +1,10 @@ +package osm.surveyor.task.user.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import osm.surveyor.task.user.model.SiteUser; + +public interface SiteUserRepository extends JpaRepository { + SiteUser findByUsername(String username); + boolean existsByUsername(String username); +} diff --git a/src/main/java/osm/surveyor/task/user/service/UserDetailsServiceImpl.java b/src/main/java/osm/surveyor/task/user/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..91bac3c --- /dev/null +++ b/src/main/java/osm/surveyor/task/user/service/UserDetailsServiceImpl.java @@ -0,0 +1,39 @@ +package osm.surveyor.task.user.service; + +import java.util.HashSet; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +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 lombok.RequiredArgsConstructor; +import osm.surveyor.task.user.model.SiteUser; +import osm.surveyor.task.user.repository.SiteUserRepository; + +@RequiredArgsConstructor +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + + private final SiteUserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + var user = userRepository.findByUsername(username); + if (user == null) { + throw new UsernameNotFoundException(username + " not found"); + } + return createUserDetails(user); + } + + public User createUserDetails(SiteUser user) { + var grantedAuthorities = new HashSet(); + grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + user.getRole())); + + return new User(user.getUsername(), user.getPassword(), grantedAuthorities); + } +} diff --git a/src/main/java/osm/surveyor/task/util/Role.java b/src/main/java/osm/surveyor/task/util/Role.java new file mode 100644 index 0000000..d32c2c9 --- /dev/null +++ b/src/main/java/osm/surveyor/task/util/Role.java @@ -0,0 +1,5 @@ +package osm.surveyor.task.util; + +public enum Role { + ADMIN, USER +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 707f9fd..a0f65e4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,7 +1,8 @@ spring.messages.fallback-to-system-locale=false -spring.datasource.url=jdbc:h2:mem:taskdb +spring.datasource.url=jdbc:h2:./taskdb spring.datasource.generate-unique-name=false spring.h2.console.enabled=true spring.jpa.show-sql=true +spring.jpa.hibernate.ddl-auto=update spring.messages.basename=org/springframework/security/messages diff --git a/src/main/resources/static/js/usertables.js b/src/main/resources/static/js/usertables.js new file mode 100644 index 0000000..d42f0fc --- /dev/null +++ b/src/main/resources/static/js/usertables.js @@ -0,0 +1,11 @@ +$(function() { + $("#user-table").dataTable({ + // DataTablesを日本語化する + language: { + url: "/webjars/datatables-plugins/i18n/Japanese.json" + }, + // 各種ボタンを有効化する + dom: "Bfrtip", + buttons: ["excelHtml5", "csvHtml5", "print"] + }); +}); diff --git a/src/main/resources/templates/fragments/sidebar.html b/src/main/resources/templates/fragments/sidebar.html index 6200151..c377bae 100644 --- a/src/main/resources/templates/fragments/sidebar.html +++ b/src/main/resources/templates/fragments/sidebar.html @@ -1,5 +1,5 @@ - + @@ -43,6 +43,12 @@ ログイン +