├── README.md
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── querydsl.gradle
├── src
├── main
│ ├── java
│ │ └── info
│ │ │ └── thecodinglive
│ │ │ └── member
│ │ │ ├── model
│ │ │ ├── MemberRole.java
│ │ │ ├── Member.java
│ │ │ ├── AbstractEntityModel.java
│ │ │ └── RememberMeToken.java
│ │ │ ├── service
│ │ │ ├── MemberService.java
│ │ │ ├── MemberServiceImpl.java
│ │ │ ├── MemberAccessDeniedHandler.java
│ │ │ ├── MyUserService.java
│ │ │ └── RememberMeTokenService.java
│ │ │ ├── repository
│ │ │ ├── MemberRepository.java
│ │ │ └── RememberMeTokenRepository.java
│ │ │ ├── MemberApp.java
│ │ │ ├── support
│ │ │ ├── PasswordEncoderUtil.java
│ │ │ └── MemberValidator.java
│ │ │ ├── config
│ │ │ ├── AuditConfig.java
│ │ │ └── SecurityConfig.java
│ │ │ └── controller
│ │ │ └── MemberController.java
│ └── resources
│ │ ├── templates
│ │ ├── admin.html
│ │ ├── fragments
│ │ │ ├── footer.html
│ │ │ ├── config.html
│ │ │ └── header.html
│ │ ├── layout
│ │ │ └── base.html
│ │ ├── welcome.html
│ │ ├── home.html
│ │ ├── error
│ │ │ └── 403.html
│ │ ├── login.html
│ │ └── signup.html
│ │ ├── sql
│ │ └── data-H2.sql
│ │ ├── validation.properties
│ │ ├── application.properties
│ │ └── static
│ │ └── css
│ │ ├── home.css
│ │ └── normalize.css
└── test
│ └── java
│ └── PasswordEncodeTest.java
├── settings.gradle
├── .gitignore
├── gradlew.bat
└── gradlew
/README.md:
--------------------------------------------------------------------------------
1 | 헤로쿠로 배포
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thecodinglive/memberApp/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/model/MemberRole.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.model;
2 |
3 |
4 | public enum MemberRole {
5 | USER, //0
6 | ADMIN //1
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/src/main/resources/templates/admin.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | admin
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Feb 21 16:33:40 KST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https://services.gradle.org/distributions/gradle-4.1-bin.zip
7 |
--------------------------------------------------------------------------------
/src/main/resources/sql/data-H2.sql:
--------------------------------------------------------------------------------
1 | insert into tbl_member (email, password, password_confirm, username, role_name) VALUES ('jins@info.com', '$2a$10$PjLG8Nr50vGQXKR63.CdMeQvvf.ZAy0ZX3BJXZnJg3FmODO9ysmt2',
2 | '$2a$10$PjLG8Nr50vGQXKR63.CdMeQvvf.ZAy0ZX3BJXZnJg3FmODO9ysmt2', '윤석진', 'ADMIN');
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/templates/fragments/footer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/service/MemberService.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.service;
2 |
3 | import info.thecodinglive.member.model.Member;
4 |
5 | public interface MemberService {
6 | void save(Member member);
7 | Member findByUserName(String username);
8 | Member findByUserEmail(String email);
9 | }
10 |
--------------------------------------------------------------------------------
/src/test/java/PasswordEncodeTest.java:
--------------------------------------------------------------------------------
1 | import info.thecodinglive.member.support.PasswordEncoderUtil;
2 | import org.junit.Test;
3 |
4 | /**
5 | * Created by ysj on 2018-03-11.
6 | */
7 | public class PasswordEncodeTest {
8 |
9 | final String pwd = "qwer1234!";
10 |
11 | @Test
12 | public void getBcrpr(){
13 | PasswordEncoderUtil pwdu = new PasswordEncoderUtil();
14 | System.out.println(pwdu.encode(pwd));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/repository/MemberRepository.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.repository;
2 |
3 | import org.springframework.data.jpa.repository.JpaRepository;
4 |
5 | import info.thecodinglive.member.model.Member;
6 | import org.springframework.stereotype.Repository;
7 |
8 | @Repository
9 | public interface MemberRepository extends JpaRepository{
10 | Member findByEmail(String email);
11 | Member findByUsername(String username);
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/repository/RememberMeTokenRepository.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.repository;
2 |
3 | import info.thecodinglive.member.model.RememberMeToken;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 | import org.springframework.stereotype.Repository;
6 |
7 | import java.util.List;
8 |
9 | @Repository
10 | public interface RememberMeTokenRepository extends JpaRepository{
11 | RememberMeToken findBySeries(String series);
12 | List findByUsername(String username);
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/resources/templates/layout/base.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * This settings file was generated by the Gradle 'init' task.
3 | *
4 | * The settings file is used to specify which projects to include in your build.
5 | * In a single project build this file can be empty or even removed.
6 | *
7 | * Detailed information about configuring a multi-project build in Gradle can be found
8 | * in the user guide at https://docs.gradle.org/3.5/userguide/multi_project_builds.html
9 | */
10 |
11 | /*
12 | // To declare projects as part of a multi-project build use the 'include' method
13 | include 'shared'
14 | include 'api'
15 | include 'services:webservice'
16 | */
17 |
18 | rootProject.name = 'bootBoard'
19 |
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/MemberApp.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.boot.autoconfigure.domain.EntityScan;
6 | import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
7 |
8 | @EntityScan(
9 | basePackageClasses = {Jsr310JpaConverters.class},
10 | basePackages = {"info.thecodinglive.member.model"})
11 | @SpringBootApplication
12 | public class MemberApp {
13 | public static void main(String ar[]){
14 | SpringApplication.run(MemberApp.class, ar);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/resources/templates/welcome.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
home
10 |
userName
11 | role:
12 |
sign out
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main/resources/validation.properties:
--------------------------------------------------------------------------------
1 | NotEmpty=\uD544\uC218\uAC12\uC785\uB2C8\uB2E4.
2 | Size.userForm.username=\uC774\uB984\uC740 3\uAE00\uC790 \uC774\uC0C1 32\uC790 \uBBF8\uB9CC\uC73C\uB85C \uC785\uB825\uD558\uC138\uC694
3 | Duplicate.userForm.username=\uC911\uBCF5\uB41C \uC774\uB984\uC785\uB2C8\uB2E4.
4 | Size.userForm.password=\uD328\uC2A4\uC6CC\uB4DC\uB294 7\uAE00\uC790 \uC774\uC0C1 32\uC790 \uBBF8\uB9CC\uC73C\uB85C \uC785\uB825\uD558\uC138\uC694
5 | Diff.userForm.passwordConfirm=\uD328\uC2A4\uC6CC\uB4DC\uAC00 \uD655\uC778\uC6A9 \uD328\uC2A4\uC6CC\uB4DC\uC640 \uC11C\uB85C \uB9DE\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.
6 | Duplicate.userForm.email=\uC774\uBBF8 \uC0AC\uC6A9\uC911\uC778 \uC774\uBA54\uC77C\uC785\uB2C8\uB2E4.
--------------------------------------------------------------------------------
/src/main/resources/templates/home.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
SpringBoot Tutorial
25 |
Jpub Java webService
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.datasource.url=jdbc:h2:mem:testdb;CACHE_SIZE=10240;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=15000;MODE=MySQL;
2 | spring.datasource.platform=h2
3 | spring.datasource.separator=;
4 | spring.datasource.username=sa
5 | spring.datasource.driver-class-name=org.h2.Driver
6 | spring.datasource.sql-script-encoding=UTF-8
7 | spring.jpa.database-platform=H2
8 | spring.jpa.hibernate.ddl-auto=update
9 | spring.datasource.data=classpath:sql/data-H2.sql
10 | spring.jpa.show-sql=true
11 |
12 | spring.thymeleaf.cache=false
13 | spring.thymeleaf.mode=LEGACYHTML5
14 |
15 | spring.devtools.livereload.enabled=true
16 | spring.messages.basename=validation
17 | spring.messages.encoding=UTF-8
18 |
19 | logging.level.org.springframework.security = debug
--------------------------------------------------------------------------------
/gradle/querydsl.gradle:
--------------------------------------------------------------------------------
1 | def querydslOutput = file('src/main/generated')
2 |
3 | task generateQueryDSL(type: JavaCompile, group: 'build', description: 'Generates the QueryDSL query types') {
4 | file(new File(projectDir, "/src/main/generated")).deleteDir()
5 | file(new File(projectDir, "/src/main/generated")).mkdirs()
6 | source = sourceSets.main.java
7 | classpath = configurations.compile + configurations.compileOnly
8 | options.compilerArgs = [
9 | "-proc:only",
10 | "-processor", 'com.querydsl.apt.jpa.JPAAnnotationProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor'
11 | ]
12 | destinationDir = file('src/main/generated')
13 | }
14 |
15 | compileJava {
16 | dependsOn generateQueryDSL
17 | }
18 |
19 | clean.doFirst{
20 | delete querydslOutput
21 | }
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/support/PasswordEncoderUtil.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.support;
2 |
3 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
4 | import org.springframework.security.crypto.password.PasswordEncoder;
5 |
6 | public class PasswordEncoderUtil implements PasswordEncoder {
7 | private PasswordEncoder passwordEncoder;
8 |
9 | public PasswordEncoderUtil() {
10 | this.passwordEncoder = new BCryptPasswordEncoder();
11 | }
12 |
13 | public PasswordEncoderUtil(PasswordEncoder passwordEncoder) {
14 | this.passwordEncoder = passwordEncoder;
15 | }
16 |
17 | @Override
18 | public String encode(CharSequence rawPassword) {
19 | return passwordEncoder.encode(rawPassword);
20 | }
21 |
22 | @Override
23 | public boolean matches(CharSequence rawPassword, String encodedPassword) {
24 | return passwordEncoder.matches(rawPassword, encodedPassword);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/model/Member.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.model;
2 |
3 | import javax.jdo.annotations.Unique;
4 | import javax.persistence.Column;
5 | import javax.persistence.Entity;
6 | import javax.persistence.EnumType;
7 | import javax.persistence.Enumerated;
8 | import javax.persistence.GeneratedValue;
9 | import javax.persistence.GenerationType;
10 | import javax.persistence.Id;
11 | import javax.persistence.Table;
12 |
13 | import lombok.Getter;
14 | import lombok.Setter;
15 | import lombok.ToString;
16 |
17 | @Getter
18 | @Setter
19 | @Entity
20 | @Table(name = "tbl_member")
21 | @ToString
22 | public class Member extends AbstractEntityModel{
23 |
24 | @Id
25 | @GeneratedValue(strategy=GenerationType.IDENTITY)
26 | private long id;
27 | private String username;
28 |
29 | private String password;
30 | private String passwordConfirm;
31 | @Unique
32 | private String email;
33 |
34 | @Column(name = "role_name")
35 | @Enumerated(EnumType.STRING)
36 | private MemberRole role;
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/service/MemberServiceImpl.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.service;
2 |
3 | import info.thecodinglive.member.repository.MemberRepository;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
7 | import org.springframework.stereotype.Service;
8 |
9 | import info.thecodinglive.member.model.Member;
10 |
11 | @Service("MemberService")
12 | @Slf4j
13 | public class MemberServiceImpl implements MemberService{
14 | @Autowired
15 | private MemberRepository memberRepository;
16 | @Autowired
17 | private BCryptPasswordEncoder bCryptPasswordEncoder;
18 |
19 | @Override
20 | public Member findByUserEmail(String email) {
21 | return memberRepository.findByEmail(email);
22 | }
23 |
24 | @Override
25 | public Member findByUserName(String username) {
26 | return memberRepository.findByUsername(username);
27 | }
28 |
29 | @Override
30 | public void save(Member member) {
31 | member.setPassword(bCryptPasswordEncoder.encode(member.getPassword()));
32 | member.setPasswordConfirm(bCryptPasswordEncoder.encode(member.getPasswordConfirm()));
33 | memberRepository.save(member);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/model/AbstractEntityModel.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.model;
2 |
3 | import java.io.Serializable;
4 | import java.time.LocalDateTime;
5 |
6 | import javax.persistence.Column;
7 | import javax.persistence.EntityListeners;
8 | import javax.persistence.MappedSuperclass;
9 |
10 | import org.springframework.data.annotation.CreatedBy;
11 | import org.springframework.data.annotation.CreatedDate;
12 | import org.springframework.data.annotation.LastModifiedBy;
13 | import org.springframework.data.annotation.LastModifiedDate;
14 | import org.springframework.data.jpa.domain.support.AuditingEntityListener;
15 |
16 | @EntityListeners(AuditingEntityListener.class)
17 | @MappedSuperclass
18 | public abstract class AbstractEntityModel implements Serializable{
19 | @CreatedDate
20 | @Column(name = "reg_date", updatable = false)
21 | private LocalDateTime createdDateTime;
22 |
23 | @LastModifiedDate
24 | @Column(name = "update_date", updatable = true)
25 | private LocalDateTime lastModifiedDateTime;
26 |
27 | @Column(name = "created_by_user", nullable = false)
28 | @CreatedBy
29 | private String createdByUser;
30 |
31 | @Column(name = "modified_by_user", nullable = false)
32 | @LastModifiedBy
33 | private String modifiedByUser;
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/model/RememberMeToken.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.model;
2 |
3 | import lombok.Getter;
4 | import lombok.NoArgsConstructor;
5 | import lombok.Setter;
6 | import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
7 |
8 | import javax.persistence.Column;
9 | import javax.persistence.Entity;
10 | import javax.persistence.Id;
11 | import javax.persistence.Table;
12 | import java.io.Serializable;
13 | import java.util.Date;
14 |
15 | @Entity
16 | @Table(name = "persistent_logins")
17 | @Getter
18 | @Setter
19 | @NoArgsConstructor
20 | public class RememberMeToken implements Serializable{
21 | @Id
22 | @Column(name = "SERIES")
23 | private String series;
24 |
25 | @Column(name = "USERNAME", nullable = false)
26 | private String username;
27 |
28 | @Column(name = "TOKEN", nullable = false)
29 | private String token;
30 |
31 | @Column(name = "LAST_USED", nullable = false)
32 | private Date lastUsed;
33 |
34 | public RememberMeToken(PersistentRememberMeToken token) {
35 | this.series = token.getSeries();
36 | this.username = token.getUsername();
37 | this.token = token.getTokenValue();
38 | this.lastUsed = token.getDate();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/service/MemberAccessDeniedHandler.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.service;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.springframework.security.access.AccessDeniedException;
5 | import org.springframework.security.core.Authentication;
6 | import org.springframework.security.core.context.SecurityContextHolder;
7 | import org.springframework.security.web.access.AccessDeniedHandler;
8 | import org.springframework.stereotype.Component;
9 |
10 | import javax.servlet.ServletException;
11 | import javax.servlet.http.HttpServletRequest;
12 | import javax.servlet.http.HttpServletResponse;
13 | import java.io.IOException;
14 |
15 | @Slf4j
16 | @Component
17 | public class MemberAccessDeniedHandler implements AccessDeniedHandler {
18 | @Override
19 | public void handle(HttpServletRequest req, HttpServletResponse res, AccessDeniedException accessDeniedException) throws IOException, ServletException {
20 | Authentication auth
21 | = SecurityContextHolder.getContext().getAuthentication();
22 |
23 | if (auth != null) {
24 | log.info("User '" + auth.getName()
25 | + "' attempted to access the protected URL: "
26 | + req.getRequestURI());
27 | }
28 |
29 | res.sendRedirect(req.getContextPath() + "/403");
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/config/AuditConfig.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.data.domain.AuditorAware;
6 | import org.springframework.data.jpa.domain.support.AuditingEntityListener;
7 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
8 | import org.springframework.security.core.Authentication;
9 | import org.springframework.security.core.context.SecurityContextHolder;
10 |
11 | @Configuration
12 | @EnableJpaAuditing
13 | public class AuditConfig {
14 | @Bean
15 | public AuditorAware createAuditorProvider() {
16 | return new SecurityAuditor();
17 | }
18 |
19 | @Bean
20 | public AuditingEntityListener createAuditingListener() {
21 | return new AuditingEntityListener();
22 | }
23 |
24 | public static class SecurityAuditor implements AuditorAware {
25 | @Override
26 | public String getCurrentAuditor() {
27 | Authentication auth = SecurityContextHolder.getContext().getAuthentication();
28 | if (null == auth || !auth.isAuthenticated()) {
29 | return null;
30 | }
31 | String username = auth.getName();
32 | return username;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/resources/static/css/home.css:
--------------------------------------------------------------------------------
1 |
2 | .intro-2 {
3 | background: url("https://mdbootstrap.com/img/Photos/Others/architecture.jpg")no-repeat center center;
4 | background-size: cover;
5 | }
6 | .btn .fa {
7 | margin-left: 3px;
8 | }
9 | .top-nav-collapse {
10 | background-color: #424f95 !important;
11 | }
12 | .navbar:not(.top-nav-collapse) {
13 | background: transparent !important;
14 | }
15 | @media (max-width: 768px) {
16 | .navbar:not(.top-nav-collapse) {
17 | background: #424f95 !important;
18 | }
19 | }
20 | h6 {
21 | line-height: 1.7;
22 | }
23 | .hm-gradient .full-bg-img {
24 | background: -moz-linear-gradient(45deg, rgba(42, 27, 161, 0.6), rgba(29, 210, 177, 0.6) 100%);
25 | background: -webkit-linear-gradient(45deg, rgba(42, 27, 161, 0.6), rgba(29, 210, 177, 0.6) 100%);
26 | background: -webkit-gradient(linear, 45deg, from(rgba(42, 27, 161, 0.6)), to(rgba(29, 210, 177, 0.6)));
27 | background: -o-linear-gradient(45deg, rgba(42, 27, 161, 0.6), rgba(29, 210, 177, 0.6) 100%);
28 | background: linear-gradient(to 45deg, rgba(42, 27, 161, 0.6), rgba(29, 210, 177, 0.6) 100%);
29 | }
30 | @media (max-width: 450px) {
31 | .margins {
32 | margin-right: 1rem;
33 | margin-left: 1rem;
34 | }
35 | }
36 | @media (max-width: 740px) {
37 | .full-height,
38 | .full-height body,
39 | .full-height header,
40 | .full-height header .view {
41 | height: 1040px;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/resources/templates/fragments/config.html:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 | jpub book example
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/support/MemberValidator.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.support;
2 |
3 | import info.thecodinglive.member.model.Member;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.stereotype.Component;
6 | import org.springframework.validation.Errors;
7 | import org.springframework.validation.ValidationUtils;
8 | import org.springframework.validation.Validator;
9 |
10 | import info.thecodinglive.member.service.MemberService;
11 |
12 | @Component
13 | public class MemberValidator implements Validator{
14 | @Autowired
15 | private MemberService memberService;
16 |
17 | @Override
18 | public boolean supports(Class> clazz) {
19 | return Member.class.equals(clazz);
20 | }
21 |
22 | @Override
23 | public void validate(Object target, Errors errors) {
24 | Member member = (Member)target;
25 |
26 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "username", "NotEmpty");
27 | if (member.getUsername().length() < 3 || member.getUsername().length() > 32) {
28 | errors.rejectValue("username", "Size.userForm.username");
29 | }
30 |
31 | if (memberService.findByUserName(member.getUsername()) != null) {
32 | errors.rejectValue("username", "Duplicate.userForm.username");
33 | }
34 |
35 | if (memberService.findByUserEmail(member.getEmail()) != null) {
36 | errors.rejectValue("email", "Duplicate.userForm.email");
37 | }
38 |
39 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "NotEmpty");
40 | if (member.getPassword().length() < 7 || member.getPassword().length() > 32) {
41 | errors.rejectValue("password", "Size.userForm.password");
42 | }
43 |
44 | if (!member.getPasswordConfirm().equals(member.getPassword())) {
45 | errors.rejectValue("passwordConfirm", "Diff.userForm.passwordConfirm");
46 | }
47 |
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/resources/templates/error/403.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Spring Boot and Thymeleaf - 403 Error
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
해당 페이지에 접근할 권한이 없습니다.
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/service/MyUserService.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.service;
2 |
3 | import java.util.HashSet;
4 | import java.util.Set;
5 |
6 | import info.thecodinglive.member.model.Member;
7 | import info.thecodinglive.member.repository.MemberRepository;
8 | import lombok.extern.slf4j.Slf4j;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.security.core.GrantedAuthority;
11 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
12 | import org.springframework.security.core.userdetails.User;
13 | import org.springframework.security.core.userdetails.UserDetails;
14 | import org.springframework.security.core.userdetails.UserDetailsService;
15 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
16 | import org.springframework.stereotype.Service;
17 | import org.springframework.transaction.annotation.Transactional;
18 |
19 | import javax.annotation.PostConstruct;
20 |
21 | @Slf4j
22 | @Service
23 | public class MyUserService implements UserDetailsService{
24 | @Autowired
25 | MemberRepository memberRepository;
26 |
27 | private static final String ROLE_PREFIX = "ROLE_";
28 |
29 | @PostConstruct
30 | private void created(){
31 | log.debug("체크 로그인");
32 | }
33 |
34 | @Override
35 | @Transactional(readOnly = true)
36 | public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
37 | log.info("loadUserByUsername user id {} ", email);
38 |
39 | Member member = memberRepository.findByEmail(email);
40 | log.debug("member", member.toString());
41 | Set grantedAuthorities = new HashSet<>();
42 |
43 | grantedAuthorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + member.getRole().name()));
44 | log.debug("권한체크:", grantedAuthorities.toString());
45 | return new User(member.getUsername(), member.getPassword(), grantedAuthorities);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/service/RememberMeTokenService.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.service;
2 |
3 | import info.thecodinglive.member.model.RememberMeToken;
4 | import info.thecodinglive.member.repository.RememberMeTokenRepository;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
8 |
9 | import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
10 | import org.springframework.stereotype.Service;
11 |
12 | import javax.transaction.Transactional;
13 | import java.util.Date;
14 | import java.util.List;
15 |
16 | @Service
17 | @Transactional
18 | @Slf4j
19 | public class RememberMeTokenService implements PersistentTokenRepository {
20 |
21 | @Autowired
22 | private RememberMeTokenRepository rememberMeTokenRepository;
23 |
24 | @Override
25 | public void createNewToken(PersistentRememberMeToken token) {
26 | RememberMeToken newToken = new RememberMeToken(token);
27 | this.rememberMeTokenRepository.save(newToken);
28 | }
29 |
30 | @Override
31 | public void updateToken(String series, String tokenValue, Date lastUsed) {
32 | RememberMeToken token = this.rememberMeTokenRepository.findBySeries(series);
33 | if (token != null){
34 | token.setToken(tokenValue);
35 | token.setLastUsed(lastUsed);
36 | this.rememberMeTokenRepository.save(token);
37 | }
38 | }
39 |
40 | @Override
41 | public PersistentRememberMeToken getTokenForSeries(String seriesId) {
42 | RememberMeToken token = this.rememberMeTokenRepository.findBySeries(seriesId);
43 | if(token != null){
44 | return new PersistentRememberMeToken(token.getToken(), token.getSeries(), token.getToken(), token.getLastUsed());
45 | }
46 | return null;
47 | }
48 |
49 | @Override
50 | public void removeUserTokens(String username) {
51 | List tokens = this.rememberMeTokenRepository.findByUsername(username);
52 | if(tokens != null){
53 | this.rememberMeTokenRepository.delete(tokens);
54 | }
55 |
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Java template
2 | *.class
3 |
4 | # Mobile Tools for Java (J2ME)
5 | .mtj.tmp/
6 |
7 | # Package Files #
8 | *.jar
9 | *.war
10 | *.ear
11 |
12 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
13 | hs_err_pid*
14 | ### JetBrains template
15 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
16 |
17 |
18 |
19 | ## Directory-based project format:
20 | .idea/
21 | # if you remove the above rule, at least ignore the following:
22 |
23 | # User-specific stuff:
24 | # .idea/workspace.xml
25 | # .idea/tasks.xml
26 | # .idea/dictionaries
27 |
28 | # Sensitive or high-churn files:
29 | # .idea/dataSources.ids
30 | # .idea/dataSources.xml
31 | # .idea/sqlDataSources.xml
32 | # .idea/dynamic.xml
33 | # .idea/uiDesigner.xml
34 |
35 | # Gradle:
36 | # .idea/gradle.xml
37 | # .idea/libraries
38 |
39 | # Mongo Explorer plugin:
40 | # .idea/mongoSettings.xml
41 |
42 | ## File-based project format:
43 |
44 | *.iws
45 |
46 | ## Plugin-specific files:
47 |
48 | # IntelliJ
49 | /out/
50 |
51 | # mpeltonen/sbt-idea plugin
52 | .idea_modules/
53 |
54 | # JIRA plugin
55 | atlassian-ide-plugin.xml
56 |
57 | # Crashlytics plugin (for Android Studio and IntelliJ)
58 | com_crashlytics_export_strings.xml
59 | crashlytics.properties
60 | crashlytics-build.properties
61 | ### Gradle template
62 | .gradle
63 | build/
64 |
65 | # Ignore Gradle GUI config
66 | gradle-app.setting
67 |
68 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
69 | !gradle-wrapper.jar
70 | ### Eclipse template
71 | *.pydevproject
72 | .metadata
73 | bin/
74 | tmp/
75 | *.tmp
76 | *.bak
77 | *.swp
78 | *~.nib
79 | local.properties
80 | .settings/
81 | .loadpath
82 |
83 | # Eclipse Core
84 | .project
85 |
86 | # External tool builders
87 | .externalToolBuilders/
88 |
89 | # Locally stored "Eclipse launch configurations"
90 | *.launch
91 |
92 | # CDT-specific
93 | .cproject
94 |
95 | # JDT-specific (Eclipse Java Development Tools)
96 | .classpath
97 |
98 | # Java annotation processor (APT)
99 | .factorypath
100 |
101 | # PDT-specific
102 | .buildpath
103 |
104 | # sbteclipse plugin
105 | .target
106 |
107 | # TeXlipse plugin
108 | .texlipse
109 |
110 | # Created by .ignore support plugin (hsz.mobi)
111 |
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/controller/MemberController.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.controller;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.stereotype.Controller;
6 | import org.springframework.ui.Model;
7 | import org.springframework.validation.BindingResult;
8 | import org.springframework.web.bind.annotation.*;
9 | import org.springframework.web.servlet.ModelAndView;
10 |
11 | import info.thecodinglive.member.model.Member;
12 | import info.thecodinglive.member.model.MemberRole;
13 | import info.thecodinglive.member.service.MemberService;
14 | import info.thecodinglive.member.support.MemberValidator;
15 |
16 | import javax.validation.Valid;
17 |
18 | @Slf4j
19 | @Controller
20 | public class MemberController {
21 | @Autowired
22 | private MemberService memberService;
23 | @Autowired
24 | private MemberValidator memberValidator;
25 |
26 | @RequestMapping(value = "/signup", method = RequestMethod.GET)
27 | public String registration(Model model) {
28 | model.addAttribute("userForm", new Member());
29 |
30 | return "signup";
31 | }
32 |
33 | @RequestMapping(value = "/signup", method = RequestMethod.POST)
34 | public String registration(@ModelAttribute("userForm") @Valid Member userForm, BindingResult bindingResult) {
35 |
36 | memberValidator.validate(userForm, bindingResult);
37 |
38 | if (bindingResult.hasErrors()) {
39 | log.debug("valid error");
40 | return "signup";
41 | }
42 |
43 | userForm.setRole(MemberRole.USER);
44 | memberService.save(userForm);
45 | log.debug("userInfo" + userForm.toString());
46 | log.debug("email" + userForm.getEmail() + "|" + userForm.getPassword());
47 |
48 | return "redirect:/welcome";
49 | }
50 |
51 | @RequestMapping(value = "/login", method = RequestMethod.GET)
52 | public ModelAndView login(Model model, String error, String logout) {
53 | if (error != null)
54 | model.addAttribute("error", "아이디 또는 패스워드가 잘못 되었습니다.");
55 |
56 | if (logout != null)
57 | model.addAttribute("message", "로그아웃 되었습니다.");
58 |
59 | return new ModelAndView("login");
60 | }
61 |
62 |
63 | @RequestMapping(value = {"/"}, method = RequestMethod.GET)
64 | public String home(Model model) {
65 | return "home";
66 | }
67 |
68 | @GetMapping("/403")
69 | public String error403() {
70 | return "/error/403";
71 | }
72 |
73 | @GetMapping("/welcome")
74 | public String welcome(){
75 | return "welcome";
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/src/main/resources/templates/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | sb
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
로그인
25 |
26 |
27 |
32 |
33 |
34 |
Logged out.
35 |
36 |
37 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/main/java/info/thecodinglive/member/config/SecurityConfig.java:
--------------------------------------------------------------------------------
1 | package info.thecodinglive.member.config;
2 |
3 | import info.thecodinglive.member.service.MemberAccessDeniedHandler;
4 | import info.thecodinglive.member.service.MyUserService;
5 | import info.thecodinglive.member.service.RememberMeTokenService;
6 | import org.springframework.boot.web.servlet.FilterRegistrationBean;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.context.annotation.Configuration;
9 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
10 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
11 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
12 | import org.springframework.security.config.annotation.web.builders.WebSecurity;
13 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
14 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
15 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
16 |
17 | import org.springframework.beans.factory.annotation.Qualifier;
18 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
19 |
20 | import javax.servlet.DispatcherType;
21 | import javax.servlet.Filter;
22 | import java.util.EnumSet;
23 |
24 | @Configuration
25 | @EnableWebSecurity
26 | @EnableGlobalMethodSecurity(prePostEnabled = true)
27 | public class SecurityConfig extends WebSecurityConfigurerAdapter {
28 | @Override
29 | protected void configure(HttpSecurity http) throws Exception {
30 | http
31 | .authorizeRequests()
32 | //접근 허용
33 | .antMatchers("/css/**", "/js/**", "/images/**", "/resources/**", "/h2-console/**", "/webjars/**", "/signup", "/home", "/").permitAll()
34 | .anyRequest().authenticated()
35 | .and()
36 | //iframe 허용
37 | .headers().
38 | frameOptions().disable()
39 | .and()
40 | //DB 어드민 접속 허용
41 | .csrf()
42 | .ignoringAntMatchers("/h2-console/**")
43 | .and()
44 | //로그인
45 | .formLogin()
46 | .loginPage("/login")
47 | .loginProcessingUrl("/sign-in")
48 | .defaultSuccessUrl("/welcome")
49 | .failureUrl("/login?error")
50 | .usernameParameter("email")
51 | .passwordParameter("passwd")
52 | .permitAll()
53 | .and()
54 | //예외처ㅣㄹ
55 | .exceptionHandling().accessDeniedHandler(MemberAccessDeniedHandler())
56 | .and()
57 | .rememberMe()
58 | .key("jpub")
59 | .rememberMeParameter("remember-me")
60 | .rememberMeCookieName("jpubcookie")
61 | .tokenValiditySeconds(86400) //1day
62 | .tokenRepository(rememberMeTokenService()).userDetailsService(myUserService())
63 | .and()
64 | //로그아웃
65 | .logout()
66 | .invalidateHttpSession(true)
67 | .clearAuthentication(true)
68 | .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
69 | .logoutSuccessUrl("/login?logout")
70 | .permitAll();
71 | }
72 |
73 |
74 | @Override
75 | protected void configure(AuthenticationManagerBuilder auth) throws Exception {
76 | auth.userDetailsService(myUserService()).passwordEncoder(bCryptPasswordEncoder());
77 | }
78 |
79 | @Bean
80 | public FilterRegistrationBean getSpringSecurityFilterChainBindedToError(
81 | @Qualifier("springSecurityFilterChain") Filter springSecurityFilterChain) {
82 |
83 | FilterRegistrationBean registration = new FilterRegistrationBean();
84 | registration.setFilter(springSecurityFilterChain);
85 | registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
86 | return registration;
87 | }
88 |
89 |
90 | @Bean
91 | public BCryptPasswordEncoder bCryptPasswordEncoder() {
92 | return new BCryptPasswordEncoder();
93 | }
94 |
95 |
96 | @Bean
97 | public MyUserService myUserService() throws Exception {
98 | return new MyUserService();
99 | }
100 |
101 | @Bean
102 | public RememberMeTokenService rememberMeTokenService() throws Exception{
103 | return new RememberMeTokenService();
104 | }
105 |
106 | @Bean
107 | public MemberAccessDeniedHandler MemberAccessDeniedHandler() throws Exception{
108 | return new MemberAccessDeniedHandler();
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/main/resources/templates/signup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | sb
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn ( ) {
37 | echo "$*"
38 | }
39 |
40 | die ( ) {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save ( ) {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/src/main/resources/templates/fragments/header.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/src/main/resources/static/css/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS and IE text size adjust after device orientation change,
6 | * without disabling user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | * and Firefox.
30 | * Correct `block` display not defined for `main` in IE 11.
31 | */
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | /**
50 | * 1. Correct `inline-block` display not defined in IE 8/9.
51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | */
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; /* 1 */
59 | vertical-align: baseline; /* 2 */
60 | }
61 |
62 | /**
63 | * Prevent modern browsers from displaying `audio` without controls.
64 | * Remove excess height in iOS 5 devices.
65 | */
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | /**
73 | * Address `[hidden]` styling not present in IE 8/9/10.
74 | * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
75 | */
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | /* Links
83 | ========================================================================== */
84 |
85 | /**
86 | * Remove the gray background color from active links in IE 10.
87 | */
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | /**
94 | * Improve readability of focused elements when they are also in an
95 | * active/hover state.
96 | */
97 |
98 | a:active,
99 | a:hover {
100 | outline: 0;
101 | }
102 |
103 | /* Text-level semantics
104 | ========================================================================== */
105 |
106 | /**
107 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
108 | */
109 |
110 | abbr[title] {
111 | border-bottom: 1px dotted;
112 | }
113 |
114 | /**
115 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
116 | */
117 |
118 | b,
119 | strong {
120 | font-weight: bold;
121 | }
122 |
123 | /**
124 | * Address styling not present in Safari and Chrome.
125 | */
126 |
127 | dfn {
128 | font-style: italic;
129 | }
130 |
131 | /**
132 | * Address variable `h1` font-size and margin within `section` and `article`
133 | * contexts in Firefox 4+, Safari, and Chrome.
134 | */
135 |
136 | h1 {
137 | font-size: 2em;
138 | margin: 0.67em 0;
139 | }
140 |
141 | /**
142 | * Address styling not present in IE 8/9.
143 | */
144 |
145 | mark {
146 | background: #ff0;
147 | color: #000;
148 | }
149 |
150 | /**
151 | * Address inconsistent and variable font size in all browsers.
152 | */
153 |
154 | small {
155 | font-size: 80%;
156 | }
157 |
158 | /**
159 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
160 | */
161 |
162 | sub,
163 | sup {
164 | font-size: 75%;
165 | line-height: 0;
166 | position: relative;
167 | vertical-align: baseline;
168 | }
169 |
170 | sup {
171 | top: -0.5em;
172 | }
173 |
174 | sub {
175 | bottom: -0.25em;
176 | }
177 |
178 | /* Embedded content
179 | ========================================================================== */
180 |
181 | /**
182 | * Remove border when inside `a` element in IE 8/9/10.
183 | */
184 |
185 | img {
186 | border: 0;
187 | }
188 |
189 | /**
190 | * Correct overflow not hidden in IE 9/10/11.
191 | */
192 |
193 | svg:not(:root) {
194 | overflow: hidden;
195 | }
196 |
197 | /* Grouping content
198 | ========================================================================== */
199 |
200 | /**
201 | * Address margin not present in IE 8/9 and Safari.
202 | */
203 |
204 | figure {
205 | margin: 1em 40px;
206 | }
207 |
208 | /**
209 | * Address differences between Firefox and other browsers.
210 | */
211 |
212 | hr {
213 | box-sizing: content-box;
214 | height: 0;
215 | }
216 |
217 | /**
218 | * Contain overflow in all browsers.
219 | */
220 |
221 | pre {
222 | overflow: auto;
223 | }
224 |
225 | /**
226 | * Address odd `em`-unit font size rendering in all browsers.
227 | */
228 |
229 | code,
230 | kbd,
231 | pre,
232 | samp {
233 | font-family: monospace, monospace;
234 | font-size: 1em;
235 | }
236 |
237 | /* Forms
238 | ========================================================================== */
239 |
240 | /**
241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
242 | * styling of `select`, unless a `border` property is set.
243 | */
244 |
245 | /**
246 | * 1. Correct color not being inherited.
247 | * Known issue: affects color of disabled elements.
248 | * 2. Correct font properties not being inherited.
249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
250 | */
251 |
252 | button,
253 | input,
254 | optgroup,
255 | select,
256 | textarea {
257 | color: inherit; /* 1 */
258 | font: inherit; /* 2 */
259 | margin: 0; /* 3 */
260 | }
261 |
262 | /**
263 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
264 | */
265 |
266 | button {
267 | overflow: visible;
268 | }
269 |
270 | /**
271 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
272 | * All other form control elements do not inherit `text-transform` values.
273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
274 | * Correct `select` style inheritance in Firefox.
275 | */
276 |
277 | button,
278 | select {
279 | text-transform: none;
280 | }
281 |
282 | /**
283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
284 | * and `video` controls.
285 | * 2. Correct inability to style clickable `input` types in iOS.
286 | * 3. Improve usability and consistency of cursor style between image-type
287 | * `input` and others.
288 | */
289 |
290 | button,
291 | html input[type="button"], /* 1 */
292 | input[type="reset"],
293 | input[type="submit"] {
294 | -webkit-appearance: button; /* 2 */
295 | cursor: pointer; /* 3 */
296 | }
297 |
298 | /**
299 | * Re-set default cursor for disabled elements.
300 | */
301 |
302 | button[disabled],
303 | html input[disabled] {
304 | cursor: default;
305 | }
306 |
307 | /**
308 | * Remove inner padding and border in Firefox 4+.
309 | */
310 |
311 | button::-moz-focus-inner,
312 | input::-moz-focus-inner {
313 | border: 0;
314 | padding: 0;
315 | }
316 |
317 | /**
318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
319 | * the UA stylesheet.
320 | */
321 |
322 | input {
323 | line-height: normal;
324 | }
325 |
326 | /**
327 | * It's recommended that you don't attempt to style these elements.
328 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
329 | *
330 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
331 | * 2. Remove excess padding in IE 8/9/10.
332 | */
333 |
334 | input[type="checkbox"],
335 | input[type="radio"] {
336 | box-sizing: border-box; /* 1 */
337 | padding: 0; /* 2 */
338 | }
339 |
340 | /**
341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
342 | * `font-size` values of the `input`, it causes the cursor style of the
343 | * decrement button to change from `default` to `text`.
344 | */
345 |
346 | input[type="number"]::-webkit-inner-spin-button,
347 | input[type="number"]::-webkit-outer-spin-button {
348 | height: auto;
349 | }
350 |
351 | /**
352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
354 | */
355 |
356 | input[type="search"] {
357 | -webkit-appearance: textfield; /* 1 */
358 | box-sizing: content-box; /* 2 */
359 | }
360 |
361 | /**
362 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
363 | * Safari (but not Chrome) clips the cancel button when the search input has
364 | * padding (and `textfield` appearance).
365 | */
366 |
367 | input[type="search"]::-webkit-search-cancel-button,
368 | input[type="search"]::-webkit-search-decoration {
369 | -webkit-appearance: none;
370 | }
371 |
372 | /**
373 | * Define consistent border, margin, and padding.
374 | */
375 |
376 | fieldset {
377 | border: 1px solid #c0c0c0;
378 | margin: 0 2px;
379 | padding: 0.35em 0.625em 0.75em;
380 | }
381 |
382 | /**
383 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
384 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
385 | */
386 |
387 | legend {
388 | border: 0; /* 1 */
389 | padding: 0; /* 2 */
390 | }
391 |
392 | /**
393 | * Remove default vertical scrollbar in IE 8/9/10/11.
394 | */
395 |
396 | textarea {
397 | overflow: auto;
398 | }
399 |
400 | /**
401 | * Don't inherit the `font-weight` (applied by a rule above).
402 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
403 | */
404 |
405 | optgroup {
406 | font-weight: bold;
407 | }
408 |
409 | /* Tables
410 | ========================================================================== */
411 |
412 | /**
413 | * Remove most spacing between table cells.
414 | */
415 |
416 | table {
417 | border-collapse: collapse;
418 | border-spacing: 0;
419 | }
420 |
421 | td,
422 | th {
423 | padding: 0;
424 | }
425 |
--------------------------------------------------------------------------------