diff --git a/SpringBootWithJWT/.gitignore b/SpringBootWithJWT/.gitignore new file mode 100644 index 0000000..63177e3 --- /dev/null +++ b/SpringBootWithJWT/.gitignore @@ -0,0 +1,30 @@ +HELP.md +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/SpringBootWithJWT/build.gradle b/SpringBootWithJWT/build.gradle new file mode 100644 index 0000000..3e7f1de --- /dev/null +++ b/SpringBootWithJWT/build.gradle @@ -0,0 +1,37 @@ +plugins { + id 'org.springframework.boot' version '2.0.4.RELEASE' + id 'java' +} + +apply plugin: 'io.spring.dependency-management' + +group = 'com.devd.spring' +version = '0.0.1-SNAPSHOT' +sourceCompatibility = '1.8' + +repositories { + mavenCentral() +} + +ext { + set('springCloudVersion', 'Greenwich.SR1') +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-web' + compile group: 'org.springframework.security', name: 'spring-security-jwt', version: '1.0.7.RELEASE' + compile group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version: '2.1.0.RELEASE' + + runtimeOnly 'com.h2database:h2' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} diff --git a/SpringBootWithJWT/gradlew b/SpringBootWithJWT/gradlew new file mode 100755 index 0000000..af6708f --- /dev/null +++ b/SpringBootWithJWT/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/SpringBootWithJWT/gradlew.bat b/SpringBootWithJWT/gradlew.bat new file mode 100644 index 0000000..0f8d593 --- /dev/null +++ b/SpringBootWithJWT/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/SpringBootWithJWT/readme.md b/SpringBootWithJWT/readme.md new file mode 100644 index 0000000..0855a10 --- /dev/null +++ b/SpringBootWithJWT/readme.md @@ -0,0 +1,23 @@ + +Admin +userName: admin.admin +password: jwtpass + +Normal User +userName: john.doe +password: jwtpass + + +To get the accessToken + +curl TestJwtClientId:Abcd1234Secret@localhost:9090/oauth/token -d grant_type=password -d username=admin.admin -d password=jwtpass + + +There are 2 rest endpoints + +/books -> needs Admin or User role to access. +/users -> needs only Admin role to access. + +After getting accessToken call rest endpoint passing accessToken in header. + +curl http://localhost:9090/api/books -H "Authorization: Bearer " \ No newline at end of file diff --git a/SpringBootWithJWT/settings.gradle b/SpringBootWithJWT/settings.gradle new file mode 100644 index 0000000..9999c42 --- /dev/null +++ b/SpringBootWithJWT/settings.gradle @@ -0,0 +1,6 @@ +pluginManagement { + repositories { + gradlePluginPortal() + } +} +rootProject.name = 'SpringBootWithJWTAndAngular' diff --git a/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/SpringBootWithJwtAndAngularApplication.java b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/SpringBootWithJwtAndAngularApplication.java new file mode 100644 index 0000000..8132f30 --- /dev/null +++ b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/SpringBootWithJwtAndAngularApplication.java @@ -0,0 +1,17 @@ +package com.devd.spring.SpringBootWithJWTAndAngular; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author: Devaraj Reddy, + * Date : 2019-04-22 20:10 + */ +@SpringBootApplication +public class SpringBootWithJwtAndAngularApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootWithJwtAndAngularApplication.class, args); + } + +} diff --git a/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/config/AuthorizationServerConfig.java b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/config/AuthorizationServerConfig.java new file mode 100755 index 0000000..7d5fec4 --- /dev/null +++ b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/config/AuthorizationServerConfig.java @@ -0,0 +1,78 @@ +package com.devd.spring.SpringBootWithJWTAndAngular.config; + +import java.util.Arrays; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.provider.token.TokenEnhancerChain; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; + +/** + * @author: Devaraj Reddy, + * Date : 2019-04-22 19:58 + */ +@Configuration +@EnableAuthorizationServer +public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { + + @Value("${security.jwt.client-id}") + private String clientId; + + @Value("${security.jwt.client-secret}") + private String clientSecret; + + @Value("${security.jwt.grant-type}") + private String grantType; + + @Value("${security.jwt.scope-read}") + private String scopeRead; + + @Value("${security.jwt.scope-write}") + private String scopeWrite = "write"; + + @Value("${security.jwt.resource-ids}") + private String resourceIds; + + @Autowired + private TokenStore tokenStore; + + @Autowired + private JwtAccessTokenConverter accessTokenConverter; + + @Autowired + private AuthenticationManager authenticationManager; + + @Autowired + private PasswordEncoder passwordEncoder; + + //In Memory Configuration for storing clientId and clientSecrets. + @Override + public void configure(ClientDetailsServiceConfigurer configurer) throws Exception { + configurer + .inMemory() + .withClient(clientId) + .secret(passwordEncoder.encode(clientSecret)) + .authorizedGrantTypes(grantType) + .scopes(scopeRead, scopeWrite) + .resourceIds(resourceIds); + } + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + TokenEnhancerChain enhancerChain = new TokenEnhancerChain(); + enhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter)); + endpoints.tokenStore(tokenStore) + .accessTokenConverter(accessTokenConverter) + .tokenEnhancer(enhancerChain) + .authenticationManager(authenticationManager); + } + +} diff --git a/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/config/DatasourceConfig.java b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/config/DatasourceConfig.java new file mode 100755 index 0000000..e298437 --- /dev/null +++ b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/config/DatasourceConfig.java @@ -0,0 +1,58 @@ +package com.devd.spring.SpringBootWithJWTAndAngular.config; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.JpaVendorAdapter; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; +import java.beans.PropertyVetoException; + +/** + * @author: Devaraj Reddy, + * Date : 2019-04-22 19:58 + */ +@Configuration +@EnableTransactionManagement +@EnableJpaRepositories(basePackages = "com.devd.spring.SpringBootWithJWTAndAngular.repository") +public class DatasourceConfig { + + @Bean + public DataSource datasource() { + EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); + EmbeddedDatabase dataSource = builder + .setType(EmbeddedDatabaseType.H2) + .addScript("sql-scripts/schema.sql") + .addScript("sql-scripts/data.sql") + .build(); + + return dataSource; + } + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("datasource") DataSource ds) throws PropertyVetoException{ + LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean(); + entityManagerFactory.setDataSource(ds); + entityManagerFactory.setPackagesToScan(new String[]{"com.devd.spring.SpringBootWithJWTAndAngular.model"}); + JpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter(); + entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter); + return entityManagerFactory; + } + + @Bean + public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory){ + JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(entityManagerFactory); + return transactionManager; + } +} \ No newline at end of file diff --git a/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/config/ResourceServerConfig.java b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/config/ResourceServerConfig.java new file mode 100755 index 0000000..08ba54b --- /dev/null +++ b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/config/ResourceServerConfig.java @@ -0,0 +1,40 @@ +package com.devd.spring.SpringBootWithJWTAndAngular.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; + +/** + * @author: Devaraj Reddy, + * Date : 2019-04-22 19:58 + */ +@Configuration +@EnableResourceServer +public class ResourceServerConfig extends ResourceServerConfigurerAdapter { + + @Autowired + private ResourceServerTokenServices tokenServices; + + @Value("${security.jwt.resource-ids}") + private String resourceIds; + + @Override + public void configure(ResourceServerSecurityConfigurer resources) throws Exception { + resources.resourceId(resourceIds).tokenServices(tokenServices); + } + + @Override + public void configure(HttpSecurity http) throws Exception { + http + .requestMatchers() + .and() + .authorizeRequests() + .antMatchers("/actuator/**", "/api-docs/**","/h2-console/**").permitAll() + .antMatchers("/api/**" ).authenticated(); + } +} diff --git a/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/config/SecurityConfig.java b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/config/SecurityConfig.java new file mode 100755 index 0000000..f7c3620 --- /dev/null +++ b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/config/SecurityConfig.java @@ -0,0 +1,81 @@ +package com.devd.spring.SpringBootWithJWTAndAngular.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; + +/** + * @author: Devaraj Reddy, + * Date : 2019-04-22 19:58 + */ +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Value("${security.signing-key}") + private String signingKey; + + @Value("${security.encoding-strength}") + private Integer encodingStrength; + + @Value("${security.security-realm}") + private String securityRealm; + + @Bean + @Override + protected AuthenticationManager authenticationManager() throws Exception { + return super.authenticationManager(); + } + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .httpBasic() + .realmName(securityRealm) + .and() + .csrf() + .disable(); + } + + @Bean + public JwtAccessTokenConverter accessTokenConverter() { + JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); + converter.setSigningKey(signingKey); + return converter; + } + + @Bean + public TokenStore tokenStore() { + return new JwtTokenStore(accessTokenConverter()); + } + + @Bean + @Primary //Making this primary to avoid any accidental duplication with another token service instance of the same name + public DefaultTokenServices tokenServices() { + DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); + defaultTokenServices.setTokenStore(tokenStore()); + defaultTokenServices.setSupportRefreshToken(true); + return defaultTokenServices; + } +} diff --git a/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/controller/ResourceController.java b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/controller/ResourceController.java new file mode 100644 index 0000000..5763e09 --- /dev/null +++ b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/controller/ResourceController.java @@ -0,0 +1,37 @@ +package com.devd.spring.SpringBootWithJWTAndAngular.controller; + +import com.devd.spring.SpringBootWithJWTAndAngular.model.Book; +import com.devd.spring.SpringBootWithJWTAndAngular.model.User; +import com.devd.spring.SpringBootWithJWTAndAngular.service.GenericService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * @author: Devaraj Reddy, + * Date : 2019-04-22 19:58 + */ +@RestController +@RequestMapping("/api") +public class ResourceController { + + @Autowired + private GenericService userService; + + @RequestMapping(value ="/books") + @PreAuthorize("hasAuthority('ADMIN_USER') or hasAuthority('STANDARD_USER')") + public List getUser(){ + return userService.findAllBooks(); + } + + @RequestMapping(value ="/users", method = RequestMethod.GET) + @PreAuthorize("hasAuthority('ADMIN_USER')") + public List getUsers(){ + return userService.findAllUsers(); + } + +} diff --git a/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/model/Book.java b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/model/Book.java new file mode 100644 index 0000000..97c7620 --- /dev/null +++ b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/model/Book.java @@ -0,0 +1,41 @@ +package com.devd.spring.SpringBootWithJWTAndAngular.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * @author: Devaraj Reddy, + * Date : 2019-04-22 20:05 + */ +@Entity +@Table(name = "book") +public class Book { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "name") + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/model/Role.java b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/model/Role.java new file mode 100644 index 0000000..29caea3 --- /dev/null +++ b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/model/Role.java @@ -0,0 +1,54 @@ +package com.devd.spring.SpringBootWithJWTAndAngular.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * @author: Devaraj Reddy, + * Date : 2019-04-22 20:06 + */ +@Entity +@Table(name="app_role") +public class Role { + + private static final long serialVersionUID = 1L; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name="role_name") + private String roleName; + + @Column(name="description") + private String description; + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + +} diff --git a/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/model/User.java b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/model/User.java new file mode 100644 index 0000000..99dd316 --- /dev/null +++ b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/model/User.java @@ -0,0 +1,103 @@ +package com.devd.spring.SpringBootWithJWTAndAngular.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.Table; +import java.util.List; + +/** + * @author: Devaraj Reddy, + * Date : 2019-04-22 20:07 + */ +@Entity +@Table(name = "app_user") +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "username") + private String username; + + @Column(name = "password") + @JsonIgnore + private String password; + + @Column(name = "first_name") + private String firstName; + + @Column(name = "last_name") + private String lastName; + + /** + * Roles are being eagerly loaded here because + * they are a fairly small collection of items for this example. + */ + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "user_role", joinColumns + = @JoinColumn(name = "user_id", + referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "role_id", + referencedColumnName = "id")) + private List roles; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + +} diff --git a/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/repository/BooksRepository.java b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/repository/BooksRepository.java new file mode 100644 index 0000000..b2ae50a --- /dev/null +++ b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/repository/BooksRepository.java @@ -0,0 +1,11 @@ +package com.devd.spring.SpringBootWithJWTAndAngular.repository; + +import com.devd.spring.SpringBootWithJWTAndAngular.model.Book; +import org.springframework.data.repository.CrudRepository; + +/** + * @author: Devaraj Reddy, + * Date : 2019-04-22 20:10 + */ +public interface BooksRepository extends CrudRepository { +} diff --git a/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/repository/RoleRepository.java b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/repository/RoleRepository.java new file mode 100644 index 0000000..a4264dc --- /dev/null +++ b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/repository/RoleRepository.java @@ -0,0 +1,11 @@ +package com.devd.spring.SpringBootWithJWTAndAngular.repository; + +import com.devd.spring.SpringBootWithJWTAndAngular.model.Role; +import org.springframework.data.repository.CrudRepository; + +/** + * @author: Devaraj Reddy, + * Date : 2019-04-22 20:10 + */ +public interface RoleRepository extends CrudRepository { +} diff --git a/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/repository/UserRepository.java b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/repository/UserRepository.java new file mode 100644 index 0000000..c1c28ba --- /dev/null +++ b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/repository/UserRepository.java @@ -0,0 +1,13 @@ +package com.devd.spring.SpringBootWithJWTAndAngular.repository; + +import com.devd.spring.SpringBootWithJWTAndAngular.model.User; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +/** + * @author: Devaraj Reddy, + * Date : 2019-04-22 20:11 + */ +public interface UserRepository extends CrudRepository { + User findByUsername(String username); +} diff --git a/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/service/GenericService.java b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/service/GenericService.java new file mode 100644 index 0000000..f449e56 --- /dev/null +++ b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/service/GenericService.java @@ -0,0 +1,19 @@ +package com.devd.spring.SpringBootWithJWTAndAngular.service; + +import com.devd.spring.SpringBootWithJWTAndAngular.model.Book; +import com.devd.spring.SpringBootWithJWTAndAngular.model.User; + +import java.util.List; + +/** + * @author: Devaraj Reddy, + * Date : 2019-04-22 20:00 + */ +public interface GenericService { + + User findByUsername(String username); + + List findAllUsers(); + + List findAllBooks(); +} diff --git a/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/service/impl/AppUserDetailsService.java b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/service/impl/AppUserDetailsService.java new file mode 100644 index 0000000..15b1164 --- /dev/null +++ b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/service/impl/AppUserDetailsService.java @@ -0,0 +1,44 @@ +package com.devd.spring.SpringBootWithJWTAndAngular.service.impl; + +import com.devd.spring.SpringBootWithJWTAndAngular.model.User; +import com.devd.spring.SpringBootWithJWTAndAngular.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +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 java.util.ArrayList; +import java.util.List; + +/** + * @author: Devaraj Reddy, + * Date : 2019-04-22 20:08 + */ +@Service +public class AppUserDetailsService implements UserDetailsService { + + @Autowired + private UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { + User user = userRepository.findByUsername(s); + + if(user == null) { + throw new UsernameNotFoundException(String.format("The username %s doesn't exist", s)); + } + + List authorities = new ArrayList<>(); + user.getRoles().forEach(role -> { + authorities.add(new SimpleGrantedAuthority(role.getRoleName())); + }); + + UserDetails userDetails = new org.springframework.security.core.userdetails. + User(user.getUsername(), user.getPassword(), authorities); + + return userDetails; + } +} diff --git a/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/service/impl/GenericServiceImpl.java b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/service/impl/GenericServiceImpl.java new file mode 100644 index 0000000..20663f9 --- /dev/null +++ b/SpringBootWithJWT/src/main/java/com/devd/spring/SpringBootWithJWTAndAngular/service/impl/GenericServiceImpl.java @@ -0,0 +1,41 @@ +package com.devd.spring.SpringBootWithJWTAndAngular.service.impl; + +import com.devd.spring.SpringBootWithJWTAndAngular.model.Book; +import com.devd.spring.SpringBootWithJWTAndAngular.model.User; +import com.devd.spring.SpringBootWithJWTAndAngular.repository.BooksRepository; +import com.devd.spring.SpringBootWithJWTAndAngular.repository.UserRepository; +import com.devd.spring.SpringBootWithJWTAndAngular.service.GenericService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author: Devaraj Reddy, + * Date : 2019-04-22 20:09 + */ +@Service +public class GenericServiceImpl implements GenericService { + + @Autowired + private UserRepository userRepository; + + @Autowired + private BooksRepository booksRepository; + + @Override + public User findByUsername(String username) { + return userRepository.findByUsername(username); + } + + @Override + public List findAllUsers() { + return (List)userRepository.findAll(); + } + + @Override + public List findAllBooks() { + return (List) booksRepository.findAll(); + } + +} diff --git a/SpringBootWithJWT/src/main/resources/application.properties b/SpringBootWithJWT/src/main/resources/application.properties new file mode 100644 index 0000000..3753b47 --- /dev/null +++ b/SpringBootWithJWT/src/main/resources/application.properties @@ -0,0 +1,17 @@ +server.port=9090 + +security.oauth2.resource.filter-order=3 + +security.signing-key=secretSign1234@Abcd +security.encoding-strength=256 +security.security-realm=Spring Boot JWT Example Realm + +security.jwt.client-id=TestJwtClientId +security.jwt.client-secret=Abcd1234Secret +security.jwt.grant-type=password +security.jwt.scope-read=read +security.jwt.scope-write=write +security.jwt.resource-ids=web + +spring.h2.console.enabled=true +spring.h2.console.settings.trace=true diff --git a/SpringBootWithJWT/src/main/resources/sql-scripts/data.sql b/SpringBootWithJWT/src/main/resources/sql-scripts/data.sql new file mode 100644 index 0000000..25ef85c --- /dev/null +++ b/SpringBootWithJWT/src/main/resources/sql-scripts/data.sql @@ -0,0 +1,19 @@ +INSERT INTO app_role (id, role_name, description) VALUES (1, 'STANDARD_USER', 'Standard User - Has no admin rights'); +INSERT INTO app_role (id, role_name, description) VALUES (2, 'ADMIN_USER', 'Admin User - Has permission to perform admin tasks'); + +-- USER +-- non-encrypted password: jwtpass +INSERT INTO app_user (id, first_name, last_name, password, username) VALUES (1, 'John', 'Doe', '$2a$10$qtH0F1m488673KwgAfFXEOWxsoZSeHqqlB/8BTt3a6gsI5c2mdlfe', 'john.doe'); +INSERT INTO app_user (id, first_name, last_name, password, username) VALUES (2, 'Admin', 'Admin', '$2a$10$qtH0F1m488673KwgAfFXEOWxsoZSeHqqlB/8BTt3a6gsI5c2mdlfe', 'admin.admin'); + + +INSERT INTO user_role(user_id, role_id) VALUES(1,1); +INSERT INTO user_role(user_id, role_id) VALUES(2,1); +INSERT INTO user_role(user_id, role_id) VALUES(2,2); + +-- Populate random city table + +INSERT INTO book(id, name) VALUES (1, 'Five Point SomeOne'); +INSERT INTO book(id, name) VALUES (2, 'SQL Basics'); +INSERT INTO book(id, name) VALUES (3, 'Cracking the coding Interview'); +INSERT INTO book(id, name) VALUES (4, 'Power of subconscious mind'); \ No newline at end of file diff --git a/SpringBootWithJWT/src/main/resources/sql-scripts/schema.sql b/SpringBootWithJWT/src/main/resources/sql-scripts/schema.sql new file mode 100644 index 0000000..201d528 --- /dev/null +++ b/SpringBootWithJWT/src/main/resources/sql-scripts/schema.sql @@ -0,0 +1,30 @@ +CREATE TABLE book ( + id bigint(20) NOT NULL AUTO_INCREMENT, + name varchar(255) DEFAULT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE app_role ( + id bigint(20) NOT NULL AUTO_INCREMENT, + description varchar(255) DEFAULT NULL, + role_name varchar(255) DEFAULT NULL, + PRIMARY KEY (id) +); + + +CREATE TABLE app_user ( + id bigint(20) NOT NULL AUTO_INCREMENT, + first_name varchar(255) NOT NULL, + last_name varchar(255) NOT NULL, + password varchar(255) NOT NULL, + username varchar(255) NOT NULL, + PRIMARY KEY (id) +); + + +CREATE TABLE user_role ( + user_id bigint(20) NOT NULL, + role_id bigint(20) NOT NULL, + CONSTRAINT FK859n2jvi8ivhui0rl0esws6o FOREIGN KEY (user_id) REFERENCES app_user (id), + CONSTRAINT FKa68196081fvovjhkek5m97n3y FOREIGN KEY (role_id) REFERENCES app_role (id) +); \ No newline at end of file diff --git a/SpringBootWithJWT/src/test/java/com/devd/spring/SpringBootWithJWTAndAngular/SpringBootWithJwtAndAngularApplicationTests.java b/SpringBootWithJWT/src/test/java/com/devd/spring/SpringBootWithJWTAndAngular/SpringBootWithJwtAndAngularApplicationTests.java new file mode 100644 index 0000000..c12ebc5 --- /dev/null +++ b/SpringBootWithJWT/src/test/java/com/devd/spring/SpringBootWithJWTAndAngular/SpringBootWithJwtAndAngularApplicationTests.java @@ -0,0 +1,16 @@ +package com.devd.spring.SpringBootWithJWTAndAngular; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootWithJwtAndAngularApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/SpringRestWithoutSpringBoot/src/main/resources/logback.xml b/SpringRestWithoutSpringBoot/src/main/resources/logback.xml index d9b5718..1b4d959 100644 --- a/SpringRestWithoutSpringBoot/src/main/resources/logback.xml +++ b/SpringRestWithoutSpringBoot/src/main/resources/logback.xml @@ -1,26 +1,32 @@ + %VAR:ec.services.servicename:.*% + - + - - - %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n - - + AppLog.log - + + [%d{yyyy-MM-dd HH:mm:ss z}] [%X{IP}] [%mdc{txnId}] <%t> %c{15} - %p: %m %n + - - - + - - + + - - - + + + + + + + + + + +