Skip to content

Commit 3603e8e

Browse files
committed
Added User creation endpoint and authentication via spring security oauth2 jwt
1 parent b648a05 commit 3603e8e

File tree

11 files changed

+331
-4
lines changed

11 files changed

+331
-4
lines changed

build.gradle

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ buildscript {
1818
//}
1919

2020
ext {
21-
spring_boot_version = '2.1.3.RELEASE'
21+
spring_boot_version = '2.1.4.RELEASE'
2222
scala_version = '2.12.8'
2323
}
2424

@@ -54,6 +54,9 @@ dependencies {
5454
implementation "org.springframework.boot:spring-boot-starter-jetty"
5555
implementation 'org.springframework.boot:spring-boot-starter-actuator'
5656
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
57+
implementation 'org.springframework.boot:spring-boot-starter-security'
58+
implementation 'org.springframework.security.oauth:spring-security-oauth2:2.3.5.RELEASE'
59+
implementation 'org.springframework.security:spring-security-jwt:1.0.9.RELEASE'
5760

5861
// Jackson Dependencies
5962
implementation "com.fasterxml.jackson.core:jackson-core:2.9.8"
@@ -65,6 +68,8 @@ dependencies {
6568
implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-scala_2.11', version: '2.9.8'
6669

6770
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.3.2'
71+
implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1'
72+
implementation group: 'org.glassfish.jaxb', name: 'jaxb-runtime', version: '2.3.1'
6873

6974
testImplementation 'org.springframework.boot:spring-boot-starter-test'
7075
}

build.sbt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ version := "0.0.1-SNAPSHOT"
44
import Dependencies._
55
import sbt.Keys.libraryDependencies
66

7-
lazy val springVersion = "2.1.3.RELEASE"
7+
lazy val springVersion = "2.1.4.RELEASE"
88

99
lazy val root = (project in file(".")).
1010
settings(
@@ -17,6 +17,9 @@ lazy val root = (project in file(".")).
1717
libraryDependencies += scalaTest % Test,
1818
libraryDependencies += "org.springframework.boot" % "spring-boot-starter-web" % springVersion,
1919
libraryDependencies += "org.springframework.boot" % "spring-boot-starter-actuator" % springVersion,
20+
libraryDependencies += "org.springframework.boot" % "spring-boot-starter-security" % springVersion,
21+
libraryDependencies += "org.springframework.security.oauth" % "spring-security-oauth2" % "2.3.5.RELEASE",
22+
libraryDependencies += "org.springframework.security" % "spring-security-jwt" % "1.0.9.RELEASE",
2023

2124
libraryDependencies += "org.springframework.boot" % "spring-boot-starter-data-mongodb" % springVersion,
2225
libraryDependencies += "com.h2database" % "h2" % "1.4.195",
@@ -28,7 +31,9 @@ lazy val root = (project in file(".")).
2831
libraryDependencies += "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.9.8",
2932
libraryDependencies += "com.fasterxml.jackson.module" % "jackson-module-scala_2.12" % "2.9.8",
3033
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.3.2",
31-
34+
libraryDependencies += "javax.xml.bind" % "jaxb-api" % "2.3.1",
35+
libraryDependencies += "org.glassfish.jaxb" % "jaxb-runtime" % "2.3.1",
36+
3237
libraryDependencies += "org.springframework.boot" % "spring-boot-starter-test" % springVersion
3338
)
3439

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package org.repl.poc.lmsdata.config
2+
3+
import org.springframework.beans.factory.annotation.Autowired
4+
import org.springframework.context.annotation.{Bean, Configuration}
5+
import org.springframework.security.authentication.AuthenticationManager
6+
import org.springframework.security.oauth2.common.OAuth2AccessToken
7+
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer
8+
import org.springframework.security.oauth2.config.annotation.web.configuration.{AuthorizationServerConfigurerAdapter, EnableAuthorizationServer}
9+
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer
10+
import org.springframework.security.oauth2.provider.OAuth2Authentication
11+
import org.springframework.security.oauth2.provider.token.{AccessTokenConverter, TokenEnhancer, TokenStore}
12+
13+
@Configuration
14+
@EnableAuthorizationServer
15+
class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
16+
17+
@Autowired
18+
private var tokenStore: TokenStore = _
19+
20+
@Autowired
21+
private var accessTokenConverter: AccessTokenConverter = _
22+
23+
@Autowired
24+
private var authenticationManager: AuthenticationManager = _
25+
26+
val CLIENT_ID: String = "defmacro-client";
27+
val CLIENT_SECRET = "$2y$12$nv64IvDN1P2EnNARkeYOgOdHSM4b7G5y97KuXG5sdv03HAqU6TNuW"; //defmacro-secret
28+
val GRANT_TYPE_PASSWORD = "password";
29+
val AUTHORIZATION_CODE = "authorization_code";
30+
val REFRESH_TOKEN = "refresh_token";
31+
val IMPLICIT = "implicit";
32+
val SCOPE_READ = "read";
33+
val SCOPE_WRITE = "write";
34+
val TRUST = "trust";
35+
val ACCESS_TOKEN_VALIDITY_SECONDS: Int = 1 * 60 * 60;
36+
val REFRESH_TOKEN_VALIDITY_SECONDS: Int = 6 * 60 * 60;
37+
38+
override def configure(clients: ClientDetailsServiceConfigurer): Unit = {
39+
clients
40+
.inMemory
41+
.withClient(CLIENT_ID)
42+
.secret(CLIENT_SECRET)
43+
.authorizedGrantTypes(GRANT_TYPE_PASSWORD, AUTHORIZATION_CODE, REFRESH_TOKEN, IMPLICIT)
44+
.scopes(SCOPE_READ, SCOPE_WRITE, TRUST)
45+
.accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS).
46+
refreshTokenValiditySeconds(REFRESH_TOKEN_VALIDITY_SECONDS)
47+
}
48+
49+
override def configure(endpoints: AuthorizationServerEndpointsConfigurer): Unit = {
50+
endpoints
51+
.tokenStore(tokenStore)
52+
.accessTokenConverter(accessTokenConverter)
53+
.authenticationManager(authenticationManager)
54+
}
55+
}
56+
57+
/*class CustomTokenEnhancer extends TokenEnhancer {
58+
override def enhance(accessToken: OAuth2AccessToken, authentication: OAuth2Authentication): OAuth2AccessToken = {
59+
return accessToken
60+
}
61+
}*/
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package org.repl.poc.lmsdata.config
2+
3+
import org.springframework.beans.factory.annotation.Autowired
4+
import org.springframework.boot.web.servlet.FilterRegistrationBean
5+
import org.springframework.context.annotation.{Bean, Configuration, Primary}
6+
import org.springframework.http.HttpMethod
7+
import org.springframework.security.authentication.AuthenticationManager
8+
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
9+
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
10+
import org.springframework.security.config.annotation.web.builders.HttpSecurity
11+
import org.springframework.security.config.annotation.web.configuration.{EnableWebSecurity, WebSecurityConfigurerAdapter}
12+
import org.springframework.security.config.http.SessionCreationPolicy
13+
import org.springframework.security.core.userdetails.UserDetailsService
14+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
15+
import org.springframework.security.oauth2.provider.token.{AccessTokenConverter, DefaultTokenServices, TokenStore}
16+
import org.springframework.security.oauth2.provider.token.store.{JwtAccessTokenConverter, JwtTokenStore}
17+
import org.springframework.web.cors.{CorsConfiguration, UrlBasedCorsConfigurationSource}
18+
import org.springframework.web.filter.CorsFilter
19+
import javax.annotation.Resource
20+
21+
@Configuration
22+
@EnableWebSecurity
23+
@EnableGlobalMethodSecurity(prePostEnabled = true)
24+
class SecurityConfig extends WebSecurityConfigurerAdapter {
25+
@Bean
26+
def accessTokenConverter(): AccessTokenConverter = {
27+
val jatc = new JwtAccessTokenConverter()
28+
jatc.setSigningKey("123")
29+
jatc.setVerifierKey("123")
30+
return jatc
31+
}
32+
33+
@Bean
34+
def tokenStore(converter: AccessTokenConverter): TokenStore = new JwtTokenStore(converter.asInstanceOf[JwtAccessTokenConverter])
35+
36+
@Bean
37+
@Primary
38+
def tokenServices(tokenStore: TokenStore): DefaultTokenServices = {
39+
val dts = new DefaultTokenServices()
40+
dts.setTokenStore(tokenStore)
41+
dts.setSupportRefreshToken(true)
42+
return dts
43+
}
44+
45+
@Bean
46+
@throws[Exception]
47+
override def authenticationManagerBean: AuthenticationManager = super.authenticationManagerBean
48+
49+
@Bean def encoder = new BCryptPasswordEncoder
50+
51+
@Resource(name = "userService")
52+
override val userDetailsService: UserDetailsService = null
53+
54+
@Autowired
55+
@throws[Exception]
56+
def globalUserDetails(auth: AuthenticationManagerBuilder): Unit = {
57+
auth.userDetailsService(userDetailsService).passwordEncoder(encoder)
58+
}
59+
60+
/*@Autowired
61+
def configureGlobal(auth: AuthenticationManagerBuilder): Unit = {
62+
auth.inMemoryAuthentication().withUser("bugbug0102").password("0102").roles("USER", "ADMIN", "BUGBUG")
63+
}
64+
65+
@throws[Exception]
66+
override protected def configure(auth: AuthenticationManagerBuilder): Unit = {
67+
//@formatter:off
68+
auth.inMemoryAuthentication.withUser("habuma")
69+
.password("password")
70+
.authorities("ROLE_USER", "ROLE_ADMIN")
71+
.and
72+
.withUser("izzy")
73+
.password("password")
74+
.authorities("ROLE_USER")
75+
//@formatter:on
76+
}*/
77+
78+
@throws[Exception]
79+
override protected def configure(http: HttpSecurity): Unit = {
80+
http
81+
.csrf.disable
82+
.anonymous.disable
83+
//.sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
84+
.authorizeRequests
85+
.antMatchers(HttpMethod.GET, "/api/v1/movies", "/error").permitAll
86+
.antMatchers(HttpMethod.POST, "/api/v1/login", "/api/v1/register", "/api/v1/tickets").permitAll
87+
}
88+
89+
/*@Bean def corsFilter: FilterRegistrationBean[CorsFilter] = {
90+
val source = new UrlBasedCorsConfigurationSource
91+
val config = new CorsConfiguration
92+
config.setAllowCredentials(true)
93+
config.addAllowedOrigin("*")
94+
config.addAllowedHeader("*")
95+
config.addAllowedMethod("*")
96+
source.registerCorsConfiguration("/**", config)
97+
val bean = new FilterRegistrationBean(new CorsFilter(source))
98+
bean.setOrder(0)
99+
bean
100+
}*/
101+
*/
102+
103+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.repl.poc.lmsdata.controller
2+
3+
import org.repl.poc.lmsdata.dto.{IdDto, ServiceResponse, UserCreateDto}
4+
import org.repl.poc.lmsdata.service.UserService
5+
import org.springframework.beans.factory.annotation.Autowired
6+
import org.springframework.http.MediaType
7+
import org.springframework.web.bind.annotation._
8+
9+
@RestController
10+
@RequestMapping(value = Array("/api"))
11+
class UserController @Autowired()(userService: UserService) {
12+
@PostMapping(value = Array("/v1/user"), consumes = Array(MediaType.APPLICATION_JSON_VALUE), produces = Array(MediaType.APPLICATION_JSON_VALUE))
13+
@ResponseBody
14+
def createUser(@RequestBody input: UserCreateDto): ServiceResponse[IdDto] = {
15+
return userService.createUser(input);
16+
}
17+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package org.repl.poc.lmsdata.dto
2+
3+
case class UserCreateDto(username: String, password: String) {
4+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.repl.poc.lmsdata.dto
2+
3+
case class UserDto(username: String) extends Item {
4+
var city: String = _
5+
var state: String = _
6+
var country: String = _
7+
var categories: List[String] = _
8+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.repl.poc.lmsdata.mongodb.model
2+
3+
import java.time.LocalDateTime
4+
5+
import com.fasterxml.jackson.annotation.JsonIgnore
6+
import org.repl.poc.lmsdata.dto.UserDto
7+
import org.springframework.data.annotation.Id
8+
import org.springframework.data.mongodb.core.index.Indexed
9+
import org.springframework.data.mongodb.core.mapping.Document
10+
11+
@Document(collection = "User")
12+
class UserMdl {
13+
@Id
14+
var id: String = _
15+
@Indexed
16+
var username: String = _
17+
@JsonIgnore
18+
var password: String = _
19+
var usernum: Long = _
20+
var firstName: String = _
21+
var lastName: String = _
22+
var city: String = _
23+
var state: String = _
24+
var country: String = _
25+
var categories: List[String] = _
26+
27+
var createdDate: LocalDateTime = _
28+
var createdByUID: String = _
29+
var modifiedDate: LocalDateTime = _
30+
var modifiedByUID: String = _
31+
32+
def createDto(): UserDto = {
33+
val retDto = UserDto(username)
34+
retDto.id = id
35+
retDto.city = city
36+
retDto.state = state
37+
retDto.country = country
38+
retDto.createdDate = createdDate
39+
retDto.createdByUID = createdByUID
40+
retDto.modifiedDate = modifiedDate
41+
retDto.modifiedByUID = modifiedByUID
42+
return retDto
43+
}
44+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.repl.poc.lmsdata.mongodb.repository
2+
3+
import org.repl.poc.lmsdata.mongodb.model.UserMdl
4+
import org.springframework.data.mongodb.repository.MongoRepository
5+
import org.springframework.data.repository.Repository
6+
7+
trait UserRepository extends MongoRepository[UserMdl, String] {
8+
def findByUsername(userId: String): UserMdl
9+
10+
}

src/main/scala/org/repl/poc/lmsdata/service/LibraryAdminService.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.repl.poc.lmsdata.service
22

33
import java.time.LocalDateTime
4-
import java.util.UUID
54

65
import org.repl.poc.lmsdata.dto.{IdDto, LibraryBranchDto, ServiceResponse, ServiceResponseError}
76
import org.repl.poc.lmsdata.mongodb.model.LibraryBranchMdl
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package org.repl.poc.lmsdata.service
2+
3+
import java.time.LocalDateTime
4+
5+
import org.springframework.beans.factory.annotation.Autowired
6+
import org.springframework.stereotype.Service
7+
import java.util
8+
9+
import org.repl.poc.lmsdata.dto.{IdDto, LibraryBranchDto, ServiceResponse, ServiceResponseError, UserCreateDto, UserDto}
10+
import org.repl.poc.lmsdata.mongodb.model.UserMdl
11+
import org.repl.poc.lmsdata.mongodb.repository.UserRepository
12+
import org.springframework.beans.factory.annotation.Autowired
13+
import org.springframework.security.core.authority.SimpleGrantedAuthority
14+
import org.springframework.security.core.userdetails.{UserDetailsService, UsernameNotFoundException}
15+
import org.springframework.security.crypto.password.PasswordEncoder
16+
17+
import scala.collection.mutable
18+
19+
@Service(value = "userService")
20+
class UserService extends UserDetailsService {
21+
@Autowired
22+
private val userRepository: UserRepository = null
23+
@Autowired private
24+
val passwordEncoder: PasswordEncoder = null
25+
26+
@throws[UsernameNotFoundException]
27+
def loadUserByUsername(userId: String): org.springframework.security.core.userdetails.User = {
28+
val user = userRepository.findByUsername(userId)
29+
if (user == null) throw new UsernameNotFoundException("Invalid username or password.")
30+
new org.springframework.security.core.userdetails.User(user.username, user.password, getAuthority)
31+
}
32+
33+
private def getAuthority = util.Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"))
34+
35+
def findAll: util.List[UserDto] = {
36+
val list = new util.ArrayList[UserDto]
37+
userRepository.findAll.iterator.forEachRemaining(mdl => {
38+
list.add(mdl.createDto())
39+
})
40+
list
41+
}
42+
43+
def createUser(input: UserCreateDto): ServiceResponse[IdDto] = {
44+
val response = new ServiceResponse[IdDto]()
45+
val errors = validateCreateInput(input)
46+
if (errors.nonEmpty) {
47+
errors.foreach(error => response.errors += error)
48+
response.success = false
49+
return response
50+
}
51+
val model = new UserMdl()
52+
model.username = input.username
53+
model.password = passwordEncoder.encode(input.password)
54+
model.createdDate = LocalDateTime.now()
55+
model.modifiedDate = LocalDateTime.now()
56+
val persistedModel = userRepository.save(model)
57+
val persistedDto = persistedModel.createDto()
58+
response.success = true
59+
response.data = Some(IdDto(persistedDto.id))
60+
response
61+
}
62+
63+
def validateCreateInput(input: UserCreateDto) :Seq[ServiceResponseError] = {
64+
val errors: mutable.MutableList[ServiceResponseError] = mutable.MutableList[ServiceResponseError]()
65+
val existingUser = userRepository.findByUsername(input.username)
66+
if (existingUser != null) {
67+
errors += ServiceResponseError("USER002", "Another user exists with same name.")
68+
}
69+
errors
70+
}
71+
}

0 commit comments

Comments
 (0)