spring security oauth
This commit is contained in:
parent
c8e67be694
commit
4a24341f71
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>spring-security-oauth</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
|
@ -0,0 +1,103 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>org.baeldung</groupId>
|
||||||
|
<artifactId>spring-security-oauth</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<name>spring-security-oauth</name>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>1.2.7.RELEASE</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>spring-security-oauth-server</module>
|
||||||
|
<module>spring-security-oauth-resource</module>
|
||||||
|
<module>spring-security-oauth-ui</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>spring-security-oauth</finalName>
|
||||||
|
<pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>${maven-compiler-plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<source>1.8</source>
|
||||||
|
<target>1.8</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-war-plugin</artifactId>
|
||||||
|
<version>${maven-war-plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<failOnMissingWebXml>false</failOnMissingWebXml>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>${maven-surefire-plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<testFailureIgnore>true</testFailureIgnore>
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/*IntegrationTest.java</exclude>
|
||||||
|
<exclude>**/*LiveTest.java</exclude>
|
||||||
|
</excludes>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<!-- <provPersistenceTarget>h2</provPersistenceTarget> -->
|
||||||
|
</systemPropertyVariables>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<!-- Spring -->
|
||||||
|
<org.springframework.version>4.1.7.RELEASE</org.springframework.version>
|
||||||
|
<org.springframework.security.version>3.2.8.RELEASE</org.springframework.security.version>
|
||||||
|
<oauth.version>2.0.7.RELEASE</oauth.version>
|
||||||
|
|
||||||
|
<!-- marshalling -->
|
||||||
|
|
||||||
|
<jackson.version>2.5.1</jackson.version>
|
||||||
|
|
||||||
|
<!-- logging -->
|
||||||
|
<org.slf4j.version>1.7.12</org.slf4j.version>
|
||||||
|
<logback.version>1.1.3</logback.version>
|
||||||
|
|
||||||
|
<!-- util -->
|
||||||
|
<guava.version>18.0</guava.version>
|
||||||
|
<commons-lang3.version>3.3.2</commons-lang3.version>
|
||||||
|
|
||||||
|
<!-- testing -->
|
||||||
|
<org.hamcrest.version>1.3</org.hamcrest.version>
|
||||||
|
<junit.version>4.11</junit.version>
|
||||||
|
<mockito.version>1.10.19</mockito.version>
|
||||||
|
|
||||||
|
<httpcore.version>4.4</httpcore.version>
|
||||||
|
<httpclient.version>4.4</httpclient.version>
|
||||||
|
|
||||||
|
<rest-assured.version>2.4.0</rest-assured.version>
|
||||||
|
|
||||||
|
<!-- Maven plugins -->
|
||||||
|
<maven-compiler-plugin.version>3.3</maven-compiler-plugin.version>
|
||||||
|
<maven-war-plugin.version>2.6</maven-war-plugin.version>
|
||||||
|
<maven-surefire-plugin.version>2.19</maven-surefire-plugin.version>
|
||||||
|
<cargo-maven2-plugin.version>1.4.16</cargo-maven2-plugin.version>
|
||||||
|
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
|
</classpath>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>spring-security-oauth-resource</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.wst.common.project.facet.core.builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.springframework.ide.eclipse.core.springbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.wst.validation.validationbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
||||||
|
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
|
||||||
|
<nature>org.springframework.ide.eclipse.core.springnature</nature>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
|
||||||
|
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>spring-security-oauth-resource</artifactId>
|
||||||
|
<name>spring-security-oauth-resource</name>
|
||||||
|
<packaging>war</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.baeldung</groupId>
|
||||||
|
<artifactId>spring-security-oauth</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-jdbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- oauth -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security.oauth</groupId>
|
||||||
|
<artifactId>spring-security-oauth2</artifactId>
|
||||||
|
<version>${oauth.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>${commons-lang3.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,36 @@
|
||||||
|
package org.baeldung.config;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.PropertySource;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||||
|
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||||
|
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@PropertySource({ "classpath:persistence.properties" })
|
||||||
|
public class OAuth2ResourceConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private Environment env;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DataSource dataSource() {
|
||||||
|
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||||
|
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
|
||||||
|
dataSource.setUrl(env.getProperty("jdbc.url"));
|
||||||
|
dataSource.setUsername(env.getProperty("jdbc.user"));
|
||||||
|
dataSource.setPassword(env.getProperty("jdbc.pass"));
|
||||||
|
return dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TokenStore tokenStore() {
|
||||||
|
return new JdbcTokenStore(dataSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.baeldung.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.context.web.SpringBootServletInitializer;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class ResourceApplication extends SpringBootServletInitializer {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ResourceApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.baeldung.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
|
||||||
|
import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
public class ResourceSecurityConfig extends GlobalMethodSecurityConfiguration {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MethodSecurityExpressionHandler createExpressionHandler() {
|
||||||
|
return new OAuth2MethodSecurityExpressionHandler();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.baeldung.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebMvc
|
||||||
|
@ComponentScan({ "org.baeldung.web.controller" })
|
||||||
|
public class ResourceWebConfig extends WebMvcConfigurerAdapter {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package org.baeldung.web.controller;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
|
||||||
|
import static org.apache.commons.lang3.RandomStringUtils.randomNumeric;
|
||||||
|
|
||||||
|
import org.baeldung.web.dto.Foo;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@EnableResourceServer
|
||||||
|
public class FooController {
|
||||||
|
|
||||||
|
public FooController() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
// API - read
|
||||||
|
@PreAuthorize("#oauth2.hasScope('read')")
|
||||||
|
@RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")
|
||||||
|
@ResponseBody
|
||||||
|
public Foo findById(@PathVariable final long id) {
|
||||||
|
return new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package org.baeldung.web.dto;
|
||||||
|
|
||||||
|
public class Foo {
|
||||||
|
private long id;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public Foo() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Foo(final long id, final String name) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(final long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(final String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
#server.contextPath=/resource
|
||||||
|
#server.port=8081
|
|
@ -0,0 +1,6 @@
|
||||||
|
################### DataSource Configuration ##########################
|
||||||
|
jdbc.driverClassName=com.mysql.jdbc.Driver
|
||||||
|
jdbc.url=jdbc:mysql://localhost:3306/oauth2?createDatabaseIfNotExist=true
|
||||||
|
jdbc.user=tutorialuser
|
||||||
|
jdbc.pass=tutorialmy5ql
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
|
</classpath>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>spring-security-oauth-server</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.wst.common.project.facet.core.builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.springframework.ide.eclipse.core.springbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.wst.validation.validationbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
||||||
|
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
|
||||||
|
<nature>org.springframework.ide.eclipse.core.springnature</nature>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
|
||||||
|
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
|
@ -0,0 +1,57 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>spring-security-oauth-server</artifactId>
|
||||||
|
|
||||||
|
<name>spring-security-oauth-server</name>
|
||||||
|
<packaging>war</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.baeldung</groupId>
|
||||||
|
<artifactId>spring-security-oauth</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-jdbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- oauth -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security.oauth</groupId>
|
||||||
|
<artifactId>spring-security-oauth2</artifactId>
|
||||||
|
<version>${oauth.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,88 @@
|
||||||
|
package org.baeldung.config;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.PropertySource;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||||
|
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
|
||||||
|
import org.springframework.jdbc.datasource.init.DatabasePopulator;
|
||||||
|
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
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.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
|
||||||
|
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||||
|
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@PropertySource({ "classpath:persistence.properties" })
|
||||||
|
@EnableAuthorizationServer
|
||||||
|
public class AuthServerOAuth2Config extends AuthorizationServerConfigurerAdapter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private Environment env;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthenticationManager authenticationManager;
|
||||||
|
|
||||||
|
@Value("classpath:schema.sql")
|
||||||
|
private Resource schemaScript;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
|
||||||
|
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
|
||||||
|
// @formatter:off
|
||||||
|
clients.jdbc(dataSource())
|
||||||
|
.withClient("clientId")
|
||||||
|
.authorizedGrantTypes("implicit")
|
||||||
|
.scopes("read","write")
|
||||||
|
.autoApprove(true);
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
|
||||||
|
endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DataSourceInitializer dataSourceInitializer(final DataSource dataSource) {
|
||||||
|
final DataSourceInitializer initializer = new DataSourceInitializer();
|
||||||
|
initializer.setDataSource(dataSource);
|
||||||
|
initializer.setDatabasePopulator(databasePopulator());
|
||||||
|
return initializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabasePopulator databasePopulator() {
|
||||||
|
final ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
|
||||||
|
populator.addScript(schemaScript);
|
||||||
|
return populator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DataSource dataSource() {
|
||||||
|
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||||
|
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
|
||||||
|
dataSource.setUrl(env.getProperty("jdbc.url"));
|
||||||
|
dataSource.setUsername(env.getProperty("jdbc.user"));
|
||||||
|
dataSource.setPassword(env.getProperty("jdbc.pass"));
|
||||||
|
return dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TokenStore tokenStore() {
|
||||||
|
return new JdbcTokenStore(dataSource());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.baeldung.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.context.web.SpringBootServletInitializer;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class ServerApplication extends SpringBootServletInitializer {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ServerApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.baeldung.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
auth.inMemoryAuthentication().withUser("john").password("123").roles("USER");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
// @formatter:off
|
||||||
|
http
|
||||||
|
.authorizeRequests()
|
||||||
|
.antMatchers("/login").permitAll()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
.and()
|
||||||
|
.formLogin().permitAll();
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
#server.contextPath=/server
|
|
@ -0,0 +1,6 @@
|
||||||
|
################### DataSource Configuration ##########################
|
||||||
|
jdbc.driverClassName=com.mysql.jdbc.Driver
|
||||||
|
jdbc.url=jdbc:mysql://localhost:3306/oauth2?createDatabaseIfNotExist=true
|
||||||
|
jdbc.user=tutorialuser
|
||||||
|
jdbc.pass=tutorialmy5ql
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
-- used in tests that use HSQL
|
||||||
|
create table if not exists oauth_client_details (
|
||||||
|
client_id VARCHAR(256) PRIMARY KEY,
|
||||||
|
resource_ids VARCHAR(256),
|
||||||
|
client_secret VARCHAR(256),
|
||||||
|
scope VARCHAR(256),
|
||||||
|
authorized_grant_types VARCHAR(256),
|
||||||
|
web_server_redirect_uri VARCHAR(256),
|
||||||
|
authorities VARCHAR(256),
|
||||||
|
access_token_validity INTEGER,
|
||||||
|
refresh_token_validity INTEGER,
|
||||||
|
additional_information VARCHAR(4096),
|
||||||
|
autoapprove VARCHAR(256)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table if not exists oauth_client_token (
|
||||||
|
token_id VARCHAR(256),
|
||||||
|
token LONG VARBINARY,
|
||||||
|
authentication_id VARCHAR(256) PRIMARY KEY,
|
||||||
|
user_name VARCHAR(256),
|
||||||
|
client_id VARCHAR(256)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table if not exists oauth_access_token (
|
||||||
|
token_id VARCHAR(256),
|
||||||
|
token LONG VARBINARY,
|
||||||
|
authentication_id VARCHAR(256) PRIMARY KEY,
|
||||||
|
user_name VARCHAR(256),
|
||||||
|
client_id VARCHAR(256),
|
||||||
|
authentication LONG VARBINARY,
|
||||||
|
refresh_token VARCHAR(256)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table if not exists oauth_refresh_token (
|
||||||
|
token_id VARCHAR(256),
|
||||||
|
token LONG VARBINARY,
|
||||||
|
authentication LONG VARBINARY
|
||||||
|
);
|
||||||
|
|
||||||
|
create table if not exists oauth_code (
|
||||||
|
code VARCHAR(256), authentication LONG VARBINARY
|
||||||
|
);
|
||||||
|
|
||||||
|
create table if not exists oauth_approvals (
|
||||||
|
userId VARCHAR(256),
|
||||||
|
clientId VARCHAR(256),
|
||||||
|
scope VARCHAR(256),
|
||||||
|
status VARCHAR(10),
|
||||||
|
expiresAt TIMESTAMP,
|
||||||
|
lastModifiedAt TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- customized oauth_client_details table
|
||||||
|
create table if not exists ClientDetails (
|
||||||
|
appId VARCHAR(256) PRIMARY KEY,
|
||||||
|
resourceIds VARCHAR(256),
|
||||||
|
appSecret VARCHAR(256),
|
||||||
|
scope VARCHAR(256),
|
||||||
|
grantTypes VARCHAR(256),
|
||||||
|
redirectUrl VARCHAR(256),
|
||||||
|
authorities VARCHAR(256),
|
||||||
|
access_token_validity INTEGER,
|
||||||
|
refresh_token_validity INTEGER,
|
||||||
|
additionalInformation VARCHAR(4096),
|
||||||
|
autoApproveScopes VARCHAR(256)
|
||||||
|
);
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
|
</classpath>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>spring-security-oauth-ui</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.wst.common.project.facet.core.builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.springframework.ide.eclipse.core.springbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.wst.validation.validationbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
||||||
|
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
|
||||||
|
<nature>org.springframework.ide.eclipse.core.springnature</nature>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
|
||||||
|
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
|
@ -0,0 +1,29 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>spring-security-oauth-ui</artifactId>
|
||||||
|
|
||||||
|
<name>spring-security-oauth-ui</name>
|
||||||
|
<packaging>war</packaging>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.baeldung</groupId>
|
||||||
|
<artifactId>spring-security-oauth</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.baeldung.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.context.web.SpringBootServletInitializer;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class UiApplication extends SpringBootServletInitializer {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(UiApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package org.baeldung.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||||
|
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
|
||||||
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebMvc
|
||||||
|
public class UiWebConfig extends WebMvcConfigurerAdapter {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
||||||
|
return new PropertySourcesPlaceholderConfigurer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureDefaultServletHandling(final DefaultServletHandlerConfigurer configurer) {
|
||||||
|
configurer.enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addViewControllers(final ViewControllerRegistry registry) {
|
||||||
|
super.addViewControllers(registry);
|
||||||
|
registry.addViewController("/index");
|
||||||
|
registry.addViewController("/oauthTemp");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
|
||||||
|
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
#server.context-path=/ui
|
||||||
|
#server.port=8080
|
|
@ -0,0 +1,539 @@
|
||||||
|
/* oauth-ng - v0.4.2 - 2015-08-27 */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// App libraries
|
||||||
|
angular.module('oauth', [
|
||||||
|
'oauth.directive', // login directive
|
||||||
|
'oauth.accessToken', // access token service
|
||||||
|
'oauth.endpoint', // oauth endpoint service
|
||||||
|
'oauth.profile', // profile model
|
||||||
|
'oauth.storage', // storage
|
||||||
|
'oauth.interceptor', // bearer token interceptor
|
||||||
|
'oauth.configuration' // token appender
|
||||||
|
])
|
||||||
|
.config(['$locationProvider','$httpProvider',
|
||||||
|
function($locationProvider, $httpProvider) {
|
||||||
|
$httpProvider.interceptors.push('ExpiredInterceptor');
|
||||||
|
}]);
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var accessTokenService = angular.module('oauth.accessToken', []);
|
||||||
|
|
||||||
|
accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', function(Storage, $rootScope, $location, $interval){
|
||||||
|
|
||||||
|
var service = {
|
||||||
|
token: null
|
||||||
|
},
|
||||||
|
oAuth2HashTokens = [ //per http://tools.ietf.org/html/rfc6749#section-4.2.2
|
||||||
|
'access_token', 'token_type', 'expires_in', 'scope', 'state',
|
||||||
|
'error','error_description'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the access token.
|
||||||
|
*/
|
||||||
|
service.get = function(){
|
||||||
|
return this.token;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets and returns the access token. It tries (in order) the following strategies:
|
||||||
|
* - takes the token from the fragment URI
|
||||||
|
* - takes the token from the sessionStorage
|
||||||
|
*/
|
||||||
|
service.set = function(){
|
||||||
|
this.setTokenFromString($location.hash());
|
||||||
|
|
||||||
|
//If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect
|
||||||
|
if(null === service.token){
|
||||||
|
setTokenFromSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.token;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the access token and remove the session.
|
||||||
|
* @returns {null}
|
||||||
|
*/
|
||||||
|
service.destroy = function(){
|
||||||
|
Storage.delete('token');
|
||||||
|
this.token = null;
|
||||||
|
return this.token;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the access token is expired.
|
||||||
|
*/
|
||||||
|
service.expired = function(){
|
||||||
|
return (this.token && this.token.expires_at && new Date(this.token.expires_at) < new Date());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the access token from a string and save it
|
||||||
|
* @param hash
|
||||||
|
*/
|
||||||
|
service.setTokenFromString = function(hash){
|
||||||
|
var params = getTokenFromString(hash);
|
||||||
|
|
||||||
|
if(params){
|
||||||
|
removeFragment();
|
||||||
|
setToken(params);
|
||||||
|
setExpiresAt();
|
||||||
|
// We have to save it again to make sure expires_at is set
|
||||||
|
// and the expiry event is set up properly
|
||||||
|
setToken(this.token);
|
||||||
|
$rootScope.$broadcast('oauth:login', service.token);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* * * * * * * * * *
|
||||||
|
* PRIVATE METHODS *
|
||||||
|
* * * * * * * * * */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the access token from the sessionStorage.
|
||||||
|
*/
|
||||||
|
var setTokenFromSession = function(){
|
||||||
|
var params = Storage.get('token');
|
||||||
|
if (params) {
|
||||||
|
setToken(params);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the access token.
|
||||||
|
*
|
||||||
|
* @param params
|
||||||
|
* @returns {*|{}}
|
||||||
|
*/
|
||||||
|
var setToken = function(params){
|
||||||
|
service.token = service.token || {}; // init the token
|
||||||
|
angular.extend(service.token, params); // set the access token params
|
||||||
|
setTokenInSession(); // save the token into the session
|
||||||
|
setExpiresAtEvent(); // event to fire when the token expires
|
||||||
|
|
||||||
|
return service.token;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the fragment URI and return an object
|
||||||
|
* @param hash
|
||||||
|
* @returns {{}}
|
||||||
|
*/
|
||||||
|
var getTokenFromString = function(hash){
|
||||||
|
var params = {},
|
||||||
|
regex = /([^&=]+)=([^&]*)/g,
|
||||||
|
m;
|
||||||
|
|
||||||
|
while ((m = regex.exec(hash)) !== null) {
|
||||||
|
params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(params.access_token || params.error){
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the access token into the session
|
||||||
|
*/
|
||||||
|
var setTokenInSession = function(){
|
||||||
|
Storage.set('token', service.token);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the access token expiration date (useful for refresh logics)
|
||||||
|
*/
|
||||||
|
var setExpiresAt = function(){
|
||||||
|
if (!service.token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(typeof(service.token.expires_in) !== 'undefined' && service.token.expires_in !== null) {
|
||||||
|
var expires_at = new Date();
|
||||||
|
expires_at.setSeconds(expires_at.getSeconds() + parseInt(service.token.expires_in)-60); // 60 seconds less to secure browser and response latency
|
||||||
|
service.token.expires_at = expires_at;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
service.token.expires_at = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the timeout at which the expired event is fired
|
||||||
|
*/
|
||||||
|
var setExpiresAtEvent = function(){
|
||||||
|
// Don't bother if there's no expires token
|
||||||
|
if (typeof(service.token.expires_at) === 'undefined' || service.token.expires_at === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var time = (new Date(service.token.expires_at))-(new Date());
|
||||||
|
if(time && time > 0){
|
||||||
|
$interval(function(){
|
||||||
|
$rootScope.$broadcast('oauth:expired', service.token);
|
||||||
|
}, time, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the oAuth2 pieces from the hash fragment
|
||||||
|
*/
|
||||||
|
var removeFragment = function(){
|
||||||
|
var curHash = $location.hash();
|
||||||
|
angular.forEach(oAuth2HashTokens,function(hashKey){
|
||||||
|
var re = new RegExp('&'+hashKey+'(=[^&]*)?|^'+hashKey+'(=[^&]*)?&?');
|
||||||
|
curHash = curHash.replace(re,'');
|
||||||
|
});
|
||||||
|
|
||||||
|
$location.hash(curHash);
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
|
||||||
|
}]);
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var endpointClient = angular.module('oauth.endpoint', []);
|
||||||
|
|
||||||
|
endpointClient.factory('Endpoint', function() {
|
||||||
|
|
||||||
|
var service = {};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Defines the authorization URL
|
||||||
|
*/
|
||||||
|
|
||||||
|
service.set = function(configuration) {
|
||||||
|
this.config = configuration;
|
||||||
|
return this.get();
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the authorization URL
|
||||||
|
*/
|
||||||
|
|
||||||
|
service.get = function( overrides ) {
|
||||||
|
var params = angular.extend( {}, service.config, overrides);
|
||||||
|
var oAuthScope = (params.scope) ? encodeURIComponent(params.scope) : '',
|
||||||
|
state = (params.state) ? encodeURIComponent(params.state) : '',
|
||||||
|
authPathHasQuery = (params.authorizePath.indexOf('?') === -1) ? false : true,
|
||||||
|
appendChar = (authPathHasQuery) ? '&' : '?', //if authorizePath has ? already append OAuth2 params
|
||||||
|
responseType = (params.responseType) ? encodeURIComponent(params.responseType) : '';
|
||||||
|
|
||||||
|
var url = params.site +
|
||||||
|
params.authorizePath +
|
||||||
|
appendChar + 'response_type=' + responseType + '&' +
|
||||||
|
'client_id=' + encodeURIComponent(params.clientId) + '&' +
|
||||||
|
'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' +
|
||||||
|
'scope=' + oAuthScope + '&' +
|
||||||
|
'state=' + state;
|
||||||
|
|
||||||
|
if( params.nonce ) {
|
||||||
|
url = url + '&nonce=' + params.nonce;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Redirects the app to the authorization URL
|
||||||
|
*/
|
||||||
|
|
||||||
|
service.redirect = function( overrides ) {
|
||||||
|
var targetLocation = this.get( overrides );
|
||||||
|
window.location.replace(targetLocation);
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
});
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var profileClient = angular.module('oauth.profile', []);
|
||||||
|
|
||||||
|
profileClient.factory('Profile', ['$http', 'AccessToken', '$rootScope', function($http, AccessToken, $rootScope) {
|
||||||
|
var service = {};
|
||||||
|
var profile;
|
||||||
|
|
||||||
|
service.find = function(uri) {
|
||||||
|
var promise = $http.get(uri, { headers: headers() });
|
||||||
|
promise.success(function(response) {
|
||||||
|
profile = response;
|
||||||
|
$rootScope.$broadcast('oauth:profile', profile);
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.get = function() {
|
||||||
|
return profile;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.set = function(resource) {
|
||||||
|
profile = resource;
|
||||||
|
return profile;
|
||||||
|
};
|
||||||
|
|
||||||
|
var headers = function() {
|
||||||
|
return { Authorization: 'Bearer ' + AccessToken.get().access_token };
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}]);
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var storageService = angular.module('oauth.storage', ['ngStorage']);
|
||||||
|
|
||||||
|
storageService.factory('Storage', ['$rootScope', '$sessionStorage', '$localStorage', function($rootScope, $sessionStorage, $localStorage){
|
||||||
|
|
||||||
|
var service = {
|
||||||
|
storage: $sessionStorage // By default
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the item from storage,
|
||||||
|
* Returns the item's previous value
|
||||||
|
*/
|
||||||
|
service.delete = function (name) {
|
||||||
|
var stored = this.get(name);
|
||||||
|
delete this.storage[name];
|
||||||
|
return stored;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the item from storage
|
||||||
|
*/
|
||||||
|
service.get = function (name) {
|
||||||
|
return this.storage[name];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the item in storage to the value specified
|
||||||
|
* Returns the item's value
|
||||||
|
*/
|
||||||
|
service.set = function (name, value) {
|
||||||
|
this.storage[name] = value;
|
||||||
|
return this.get(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the storage service being used
|
||||||
|
*/
|
||||||
|
service.use = function (storage) {
|
||||||
|
if (storage === 'sessionStorage') {
|
||||||
|
this.storage = $sessionStorage;
|
||||||
|
} else if (storage === 'localStorage') {
|
||||||
|
this.storage = $localStorage;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}]);
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var oauthConfigurationService = angular.module('oauth.configuration', []);
|
||||||
|
|
||||||
|
oauthConfigurationService.provider('OAuthConfiguration', function() {
|
||||||
|
var _config = {};
|
||||||
|
|
||||||
|
this.init = function(config, httpProvider) {
|
||||||
|
_config.protectedResources = config.protectedResources || [];
|
||||||
|
httpProvider.interceptors.push('AuthInterceptor');
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$get = function() {
|
||||||
|
return {
|
||||||
|
getConfig: function() {
|
||||||
|
return _config;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.factory('AuthInterceptor', function($q, $rootScope, OAuthConfiguration, AccessToken) {
|
||||||
|
return {
|
||||||
|
'request': function(config) {
|
||||||
|
OAuthConfiguration.getConfig().protectedResources.forEach(function(resource) {
|
||||||
|
// If the url is one of the protected resources, we want to see if there's a token and then
|
||||||
|
// add the token if it exists.
|
||||||
|
if (config.url.indexOf(resource) > -1) {
|
||||||
|
var token = AccessToken.get();
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = 'Bearer ' + token.access_token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var interceptorService = angular.module('oauth.interceptor', []);
|
||||||
|
|
||||||
|
interceptorService.factory('ExpiredInterceptor', ['Storage', '$rootScope', function (Storage, $rootScope) {
|
||||||
|
|
||||||
|
var service = {};
|
||||||
|
|
||||||
|
service.request = function(config) {
|
||||||
|
var token = Storage.get('token');
|
||||||
|
|
||||||
|
if (token && expired(token)) {
|
||||||
|
$rootScope.$broadcast('oauth:expired', token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
var expired = function(token) {
|
||||||
|
return (token && token.expires_at && new Date(token.expires_at) < new Date());
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}]);
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var directives = angular.module('oauth.directive', []);
|
||||||
|
|
||||||
|
directives.directive('oauth', [
|
||||||
|
'AccessToken',
|
||||||
|
'Endpoint',
|
||||||
|
'Profile',
|
||||||
|
'Storage',
|
||||||
|
'$location',
|
||||||
|
'$rootScope',
|
||||||
|
'$compile',
|
||||||
|
'$http',
|
||||||
|
'$templateCache',
|
||||||
|
function(AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache) {
|
||||||
|
|
||||||
|
var definition = {
|
||||||
|
restrict: 'AE',
|
||||||
|
replace: true,
|
||||||
|
scope: {
|
||||||
|
site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com)
|
||||||
|
clientId: '@', // (required) client id
|
||||||
|
redirectUri: '@', // (required) client redirect uri
|
||||||
|
responseType: '@', // (optional) response type, defaults to token (use 'token' for implicit flow and 'code' for authorization code flow
|
||||||
|
scope: '@', // (optional) scope
|
||||||
|
profileUri: '@', // (optional) user profile uri (e.g http://example.com/me)
|
||||||
|
template: '@', // (optional) template to render (e.g bower_components/oauth-ng/dist/views/templates/default.html)
|
||||||
|
text: '@', // (optional) login text
|
||||||
|
authorizePath: '@', // (optional) authorization url
|
||||||
|
state: '@', // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery
|
||||||
|
storage: '@' // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
definition.link = function postLink(scope, element) {
|
||||||
|
scope.show = 'none';
|
||||||
|
|
||||||
|
scope.$watch('clientId', function() {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
var init = function() {
|
||||||
|
initAttributes(); // sets defaults
|
||||||
|
Storage.use(scope.storage);// set storage
|
||||||
|
compile(); // compiles the desired layout
|
||||||
|
Endpoint.set(scope); // sets the oauth authorization url
|
||||||
|
AccessToken.set(scope); // sets the access token object (if existing, from fragment or session)
|
||||||
|
initProfile(scope); // gets the profile resource (if existing the access token)
|
||||||
|
initView(); // sets the view (logged in or out)
|
||||||
|
};
|
||||||
|
|
||||||
|
var initAttributes = function() {
|
||||||
|
scope.authorizePath = scope.authorizePath || '/oauth/authorize';
|
||||||
|
scope.tokenPath = scope.tokenPath || '/oauth/token';
|
||||||
|
scope.template = scope.template || 'bower_components/oauth-ng/dist/views/templates/default.html';
|
||||||
|
scope.responseType = scope.responseType || 'token';
|
||||||
|
scope.text = scope.text || 'Sign In';
|
||||||
|
scope.state = scope.state || undefined;
|
||||||
|
scope.scope = scope.scope || undefined;
|
||||||
|
scope.storage = scope.storage || 'sessionStorage';
|
||||||
|
};
|
||||||
|
|
||||||
|
var compile = function() {
|
||||||
|
$http.get(scope.template, { cache: $templateCache }).success(function(html) {
|
||||||
|
element.html(html);
|
||||||
|
$compile(element.contents())(scope);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var initProfile = function(scope) {
|
||||||
|
var token = AccessToken.get();
|
||||||
|
|
||||||
|
if (token && token.access_token && scope.profileUri) {
|
||||||
|
Profile.find(scope.profileUri).success(function(response) {
|
||||||
|
scope.profile = response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var initView = function() {
|
||||||
|
var token = AccessToken.get();
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return loggedOut(); // without access token it's logged out
|
||||||
|
}
|
||||||
|
if (token.access_token) {
|
||||||
|
return authorized(); // if there is the access token we are done
|
||||||
|
}
|
||||||
|
if (token.error) {
|
||||||
|
return denied(); // if the request has been denied we fire the denied event
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.login = function() {
|
||||||
|
Endpoint.redirect();
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.logout = function() {
|
||||||
|
AccessToken.destroy(scope);
|
||||||
|
$rootScope.$broadcast('oauth:logout');
|
||||||
|
loggedOut();
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.$on('oauth:expired', function() {
|
||||||
|
AccessToken.destroy(scope);
|
||||||
|
scope.show = 'logged-out';
|
||||||
|
});
|
||||||
|
|
||||||
|
// user is authorized
|
||||||
|
var authorized = function() {
|
||||||
|
$rootScope.$broadcast('oauth:authorized', AccessToken.get());
|
||||||
|
scope.show = 'logged-in';
|
||||||
|
};
|
||||||
|
|
||||||
|
// set the oauth directive to the logged-out status
|
||||||
|
var loggedOut = function() {
|
||||||
|
$rootScope.$broadcast('oauth:loggedOut');
|
||||||
|
scope.show = 'logged-out';
|
||||||
|
};
|
||||||
|
|
||||||
|
// set the oauth directive to the denied status
|
||||||
|
var denied = function() {
|
||||||
|
scope.show = 'denied';
|
||||||
|
$rootScope.$broadcast('oauth:denied');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Updates the template at runtime
|
||||||
|
scope.$on('oauth:template:update', function(event, template) {
|
||||||
|
scope.template = template;
|
||||||
|
compile(scope);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hack to update the directive content on logout
|
||||||
|
// TODO think to a cleaner solution
|
||||||
|
scope.$on('$routeChangeSuccess', function () {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return definition;
|
||||||
|
}
|
||||||
|
]);
|
|
@ -0,0 +1,56 @@
|
||||||
|
<div>
|
||||||
|
<nav class="navbar navbar-default">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<a class="navbar-brand" th:href="@{/}">Spring Security OAuth</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div><!-- /.container-fluid -->
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
<oauth
|
||||||
|
site="http://localhost:8080/spring-security-oauth-server"
|
||||||
|
client-id="clientId"
|
||||||
|
redirect-uri="http://localhost:8080/spring-security-oauth-ui/index"
|
||||||
|
scope="read"
|
||||||
|
template="oauthTemp">
|
||||||
|
</oauth>
|
||||||
|
|
||||||
|
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
|
||||||
|
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-resource.min.js"></script>
|
||||||
|
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-route.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/ngStorage/0.3.9/ngStorage.min.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
<script th:src="@{/resources/oauth-ng.js}"></script>
|
||||||
|
<script>
|
||||||
|
/*<![CDATA[*/
|
||||||
|
var app = angular.module('myApp', ["ngResource","ngRoute","oauth"]);
|
||||||
|
app.config(function($locationProvider) {
|
||||||
|
$locationProvider.html5Mode({
|
||||||
|
enabled: true,
|
||||||
|
requireBase: false
|
||||||
|
}).hashPrefix('!');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app.controller('mainCtrl', function($scope,$resource,$http) {
|
||||||
|
|
||||||
|
$scope.$on('oauth:login', function(event, token) {
|
||||||
|
$http.defaults.headers.common.Authorization= 'Bearer ' + token.access_token;
|
||||||
|
console.log('Authorized third party app with token', token.access_token);
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.foo = {id:0 , name:"sample foo"};
|
||||||
|
$scope.foos = $resource("http://localhost:8080/spring-security-oauth-resource/foos/:fooId",{fooId:'@id'});
|
||||||
|
|
||||||
|
$scope.getFoo = function(){
|
||||||
|
$scope.foo = $scope.foos.get({fooId:$scope.foo.id});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
/*]]>*/
|
||||||
|
</script>
|
||||||
|
</div>
|
|
@ -0,0 +1,30 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<title>Spring Security OAuth</title>
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"/>
|
||||||
|
</head>
|
||||||
|
<body ng-app="myApp" ng-controller="mainCtrl">
|
||||||
|
|
||||||
|
<div th:include="header"></div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="col-sm-12">Foo Details</h1>
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<label class="col-sm-3">ID</label>
|
||||||
|
<span>{{foo.id}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<label class="col-sm-3">Name</label>
|
||||||
|
<span>{{foo.name}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<a class="btn btn-default" href="#" ng-click="getFoo()">New Foo</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<div class="container">
|
||||||
|
<span class="col-sm-12">
|
||||||
|
<a href="#" class="btn btn-primary btn-lg" ng-show="show=='logged-out'" ng-click="login()">Login</a>
|
||||||
|
<a href="#" class="btn btn-primary btn-lg" ng-show="show=='denied'" ng-click="login()">Access denied. Try again.</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
|
@ -0,0 +1,539 @@
|
||||||
|
/* oauth-ng - v0.4.2 - 2015-08-27 */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// App libraries
|
||||||
|
angular.module('oauth', [
|
||||||
|
'oauth.directive', // login directive
|
||||||
|
'oauth.accessToken', // access token service
|
||||||
|
'oauth.endpoint', // oauth endpoint service
|
||||||
|
'oauth.profile', // profile model
|
||||||
|
'oauth.storage', // storage
|
||||||
|
'oauth.interceptor', // bearer token interceptor
|
||||||
|
'oauth.configuration' // token appender
|
||||||
|
])
|
||||||
|
.config(['$locationProvider','$httpProvider',
|
||||||
|
function($locationProvider, $httpProvider) {
|
||||||
|
$httpProvider.interceptors.push('ExpiredInterceptor');
|
||||||
|
}]);
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var accessTokenService = angular.module('oauth.accessToken', []);
|
||||||
|
|
||||||
|
accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', function(Storage, $rootScope, $location, $interval){
|
||||||
|
|
||||||
|
var service = {
|
||||||
|
token: null
|
||||||
|
},
|
||||||
|
oAuth2HashTokens = [ //per http://tools.ietf.org/html/rfc6749#section-4.2.2
|
||||||
|
'access_token', 'token_type', 'expires_in', 'scope', 'state',
|
||||||
|
'error','error_description'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the access token.
|
||||||
|
*/
|
||||||
|
service.get = function(){
|
||||||
|
return this.token;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets and returns the access token. It tries (in order) the following strategies:
|
||||||
|
* - takes the token from the fragment URI
|
||||||
|
* - takes the token from the sessionStorage
|
||||||
|
*/
|
||||||
|
service.set = function(){
|
||||||
|
this.setTokenFromString($location.hash());
|
||||||
|
|
||||||
|
//If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect
|
||||||
|
if(null === service.token){
|
||||||
|
setTokenFromSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.token;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the access token and remove the session.
|
||||||
|
* @returns {null}
|
||||||
|
*/
|
||||||
|
service.destroy = function(){
|
||||||
|
Storage.delete('token');
|
||||||
|
this.token = null;
|
||||||
|
return this.token;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if the access token is expired.
|
||||||
|
*/
|
||||||
|
service.expired = function(){
|
||||||
|
return (this.token && this.token.expires_at && new Date(this.token.expires_at) < new Date());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the access token from a string and save it
|
||||||
|
* @param hash
|
||||||
|
*/
|
||||||
|
service.setTokenFromString = function(hash){
|
||||||
|
var params = getTokenFromString(hash);
|
||||||
|
|
||||||
|
if(params){
|
||||||
|
removeFragment();
|
||||||
|
setToken(params);
|
||||||
|
setExpiresAt();
|
||||||
|
// We have to save it again to make sure expires_at is set
|
||||||
|
// and the expiry event is set up properly
|
||||||
|
setToken(this.token);
|
||||||
|
$rootScope.$broadcast('oauth:login', service.token);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* * * * * * * * * *
|
||||||
|
* PRIVATE METHODS *
|
||||||
|
* * * * * * * * * */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the access token from the sessionStorage.
|
||||||
|
*/
|
||||||
|
var setTokenFromSession = function(){
|
||||||
|
var params = Storage.get('token');
|
||||||
|
if (params) {
|
||||||
|
setToken(params);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the access token.
|
||||||
|
*
|
||||||
|
* @param params
|
||||||
|
* @returns {*|{}}
|
||||||
|
*/
|
||||||
|
var setToken = function(params){
|
||||||
|
service.token = service.token || {}; // init the token
|
||||||
|
angular.extend(service.token, params); // set the access token params
|
||||||
|
setTokenInSession(); // save the token into the session
|
||||||
|
setExpiresAtEvent(); // event to fire when the token expires
|
||||||
|
|
||||||
|
return service.token;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the fragment URI and return an object
|
||||||
|
* @param hash
|
||||||
|
* @returns {{}}
|
||||||
|
*/
|
||||||
|
var getTokenFromString = function(hash){
|
||||||
|
var params = {},
|
||||||
|
regex = /([^&=]+)=([^&]*)/g,
|
||||||
|
m;
|
||||||
|
|
||||||
|
while ((m = regex.exec(hash)) !== null) {
|
||||||
|
params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(params.access_token || params.error){
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the access token into the session
|
||||||
|
*/
|
||||||
|
var setTokenInSession = function(){
|
||||||
|
Storage.set('token', service.token);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the access token expiration date (useful for refresh logics)
|
||||||
|
*/
|
||||||
|
var setExpiresAt = function(){
|
||||||
|
if (!service.token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(typeof(service.token.expires_in) !== 'undefined' && service.token.expires_in !== null) {
|
||||||
|
var expires_at = new Date();
|
||||||
|
expires_at.setSeconds(expires_at.getSeconds() + parseInt(service.token.expires_in)-60); // 60 seconds less to secure browser and response latency
|
||||||
|
service.token.expires_at = expires_at;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
service.token.expires_at = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the timeout at which the expired event is fired
|
||||||
|
*/
|
||||||
|
var setExpiresAtEvent = function(){
|
||||||
|
// Don't bother if there's no expires token
|
||||||
|
if (typeof(service.token.expires_at) === 'undefined' || service.token.expires_at === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var time = (new Date(service.token.expires_at))-(new Date());
|
||||||
|
if(time && time > 0){
|
||||||
|
$interval(function(){
|
||||||
|
$rootScope.$broadcast('oauth:expired', service.token);
|
||||||
|
}, time, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the oAuth2 pieces from the hash fragment
|
||||||
|
*/
|
||||||
|
var removeFragment = function(){
|
||||||
|
var curHash = $location.hash();
|
||||||
|
angular.forEach(oAuth2HashTokens,function(hashKey){
|
||||||
|
var re = new RegExp('&'+hashKey+'(=[^&]*)?|^'+hashKey+'(=[^&]*)?&?');
|
||||||
|
curHash = curHash.replace(re,'');
|
||||||
|
});
|
||||||
|
|
||||||
|
$location.hash(curHash);
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
|
||||||
|
}]);
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var endpointClient = angular.module('oauth.endpoint', []);
|
||||||
|
|
||||||
|
endpointClient.factory('Endpoint', function() {
|
||||||
|
|
||||||
|
var service = {};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Defines the authorization URL
|
||||||
|
*/
|
||||||
|
|
||||||
|
service.set = function(configuration) {
|
||||||
|
this.config = configuration;
|
||||||
|
return this.get();
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the authorization URL
|
||||||
|
*/
|
||||||
|
|
||||||
|
service.get = function( overrides ) {
|
||||||
|
var params = angular.extend( {}, service.config, overrides);
|
||||||
|
var oAuthScope = (params.scope) ? encodeURIComponent(params.scope) : '',
|
||||||
|
state = (params.state) ? encodeURIComponent(params.state) : '',
|
||||||
|
authPathHasQuery = (params.authorizePath.indexOf('?') === -1) ? false : true,
|
||||||
|
appendChar = (authPathHasQuery) ? '&' : '?', //if authorizePath has ? already append OAuth2 params
|
||||||
|
responseType = (params.responseType) ? encodeURIComponent(params.responseType) : '';
|
||||||
|
|
||||||
|
var url = params.site +
|
||||||
|
params.authorizePath +
|
||||||
|
appendChar + 'response_type=' + responseType + '&' +
|
||||||
|
'client_id=' + encodeURIComponent(params.clientId) + '&' +
|
||||||
|
'redirect_uri=' + encodeURIComponent(params.redirectUri) + '&' +
|
||||||
|
'scope=' + oAuthScope + '&' +
|
||||||
|
'state=' + state;
|
||||||
|
|
||||||
|
if( params.nonce ) {
|
||||||
|
url = url + '&nonce=' + params.nonce;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Redirects the app to the authorization URL
|
||||||
|
*/
|
||||||
|
|
||||||
|
service.redirect = function( overrides ) {
|
||||||
|
var targetLocation = this.get( overrides );
|
||||||
|
window.location.replace(targetLocation);
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
});
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var profileClient = angular.module('oauth.profile', []);
|
||||||
|
|
||||||
|
profileClient.factory('Profile', ['$http', 'AccessToken', '$rootScope', function($http, AccessToken, $rootScope) {
|
||||||
|
var service = {};
|
||||||
|
var profile;
|
||||||
|
|
||||||
|
service.find = function(uri) {
|
||||||
|
var promise = $http.get(uri, { headers: headers() });
|
||||||
|
promise.success(function(response) {
|
||||||
|
profile = response;
|
||||||
|
$rootScope.$broadcast('oauth:profile', profile);
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.get = function() {
|
||||||
|
return profile;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.set = function(resource) {
|
||||||
|
profile = resource;
|
||||||
|
return profile;
|
||||||
|
};
|
||||||
|
|
||||||
|
var headers = function() {
|
||||||
|
return { Authorization: 'Bearer ' + AccessToken.get().access_token };
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}]);
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var storageService = angular.module('oauth.storage', ['ngStorage']);
|
||||||
|
|
||||||
|
storageService.factory('Storage', ['$rootScope', '$sessionStorage', '$localStorage', function($rootScope, $sessionStorage, $localStorage){
|
||||||
|
|
||||||
|
var service = {
|
||||||
|
storage: $sessionStorage // By default
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the item from storage,
|
||||||
|
* Returns the item's previous value
|
||||||
|
*/
|
||||||
|
service.delete = function (name) {
|
||||||
|
var stored = this.get(name);
|
||||||
|
delete this.storage[name];
|
||||||
|
return stored;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the item from storage
|
||||||
|
*/
|
||||||
|
service.get = function (name) {
|
||||||
|
return this.storage[name];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the item in storage to the value specified
|
||||||
|
* Returns the item's value
|
||||||
|
*/
|
||||||
|
service.set = function (name, value) {
|
||||||
|
this.storage[name] = value;
|
||||||
|
return this.get(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the storage service being used
|
||||||
|
*/
|
||||||
|
service.use = function (storage) {
|
||||||
|
if (storage === 'sessionStorage') {
|
||||||
|
this.storage = $sessionStorage;
|
||||||
|
} else if (storage === 'localStorage') {
|
||||||
|
this.storage = $localStorage;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}]);
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var oauthConfigurationService = angular.module('oauth.configuration', []);
|
||||||
|
|
||||||
|
oauthConfigurationService.provider('OAuthConfiguration', function() {
|
||||||
|
var _config = {};
|
||||||
|
|
||||||
|
this.init = function(config, httpProvider) {
|
||||||
|
_config.protectedResources = config.protectedResources || [];
|
||||||
|
httpProvider.interceptors.push('AuthInterceptor');
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$get = function() {
|
||||||
|
return {
|
||||||
|
getConfig: function() {
|
||||||
|
return _config;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.factory('AuthInterceptor', function($q, $rootScope, OAuthConfiguration, AccessToken) {
|
||||||
|
return {
|
||||||
|
'request': function(config) {
|
||||||
|
OAuthConfiguration.getConfig().protectedResources.forEach(function(resource) {
|
||||||
|
// If the url is one of the protected resources, we want to see if there's a token and then
|
||||||
|
// add the token if it exists.
|
||||||
|
if (config.url.indexOf(resource) > -1) {
|
||||||
|
var token = AccessToken.get();
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = 'Bearer ' + token.access_token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var interceptorService = angular.module('oauth.interceptor', []);
|
||||||
|
|
||||||
|
interceptorService.factory('ExpiredInterceptor', ['Storage', '$rootScope', function (Storage, $rootScope) {
|
||||||
|
|
||||||
|
var service = {};
|
||||||
|
|
||||||
|
service.request = function(config) {
|
||||||
|
var token = Storage.get('token');
|
||||||
|
|
||||||
|
if (token && expired(token)) {
|
||||||
|
$rootScope.$broadcast('oauth:expired', token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
var expired = function(token) {
|
||||||
|
return (token && token.expires_at && new Date(token.expires_at) < new Date());
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}]);
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var directives = angular.module('oauth.directive', []);
|
||||||
|
|
||||||
|
directives.directive('oauth', [
|
||||||
|
'AccessToken',
|
||||||
|
'Endpoint',
|
||||||
|
'Profile',
|
||||||
|
'Storage',
|
||||||
|
'$location',
|
||||||
|
'$rootScope',
|
||||||
|
'$compile',
|
||||||
|
'$http',
|
||||||
|
'$templateCache',
|
||||||
|
function(AccessToken, Endpoint, Profile, Storage, $location, $rootScope, $compile, $http, $templateCache) {
|
||||||
|
|
||||||
|
var definition = {
|
||||||
|
restrict: 'AE',
|
||||||
|
replace: true,
|
||||||
|
scope: {
|
||||||
|
site: '@', // (required) set the oauth server host (e.g. http://oauth.example.com)
|
||||||
|
clientId: '@', // (required) client id
|
||||||
|
redirectUri: '@', // (required) client redirect uri
|
||||||
|
responseType: '@', // (optional) response type, defaults to token (use 'token' for implicit flow and 'code' for authorization code flow
|
||||||
|
scope: '@', // (optional) scope
|
||||||
|
profileUri: '@', // (optional) user profile uri (e.g http://example.com/me)
|
||||||
|
template: '@', // (optional) template to render (e.g bower_components/oauth-ng/dist/views/templates/default.html)
|
||||||
|
text: '@', // (optional) login text
|
||||||
|
authorizePath: '@', // (optional) authorization url
|
||||||
|
state: '@', // (optional) An arbitrary unique string created by your app to guard against Cross-site Request Forgery
|
||||||
|
storage: '@' // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
definition.link = function postLink(scope, element) {
|
||||||
|
scope.show = 'none';
|
||||||
|
|
||||||
|
scope.$watch('clientId', function() {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
var init = function() {
|
||||||
|
initAttributes(); // sets defaults
|
||||||
|
Storage.use(scope.storage);// set storage
|
||||||
|
compile(); // compiles the desired layout
|
||||||
|
Endpoint.set(scope); // sets the oauth authorization url
|
||||||
|
AccessToken.set(scope); // sets the access token object (if existing, from fragment or session)
|
||||||
|
initProfile(scope); // gets the profile resource (if existing the access token)
|
||||||
|
initView(); // sets the view (logged in or out)
|
||||||
|
};
|
||||||
|
|
||||||
|
var initAttributes = function() {
|
||||||
|
scope.authorizePath = scope.authorizePath || '/oauth/authorize';
|
||||||
|
scope.tokenPath = scope.tokenPath || '/oauth/token';
|
||||||
|
scope.template = scope.template || 'bower_components/oauth-ng/dist/views/templates/default.html';
|
||||||
|
scope.responseType = scope.responseType || 'token';
|
||||||
|
scope.text = scope.text || 'Sign In';
|
||||||
|
scope.state = scope.state || undefined;
|
||||||
|
scope.scope = scope.scope || undefined;
|
||||||
|
scope.storage = scope.storage || 'sessionStorage';
|
||||||
|
};
|
||||||
|
|
||||||
|
var compile = function() {
|
||||||
|
$http.get(scope.template, { cache: $templateCache }).success(function(html) {
|
||||||
|
element.html(html);
|
||||||
|
$compile(element.contents())(scope);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var initProfile = function(scope) {
|
||||||
|
var token = AccessToken.get();
|
||||||
|
|
||||||
|
if (token && token.access_token && scope.profileUri) {
|
||||||
|
Profile.find(scope.profileUri).success(function(response) {
|
||||||
|
scope.profile = response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var initView = function() {
|
||||||
|
var token = AccessToken.get();
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return loggedOut(); // without access token it's logged out
|
||||||
|
}
|
||||||
|
if (token.access_token) {
|
||||||
|
return authorized(); // if there is the access token we are done
|
||||||
|
}
|
||||||
|
if (token.error) {
|
||||||
|
return denied(); // if the request has been denied we fire the denied event
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.login = function() {
|
||||||
|
Endpoint.redirect();
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.logout = function() {
|
||||||
|
AccessToken.destroy(scope);
|
||||||
|
$rootScope.$broadcast('oauth:logout');
|
||||||
|
loggedOut();
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.$on('oauth:expired', function() {
|
||||||
|
AccessToken.destroy(scope);
|
||||||
|
scope.show = 'logged-out';
|
||||||
|
});
|
||||||
|
|
||||||
|
// user is authorized
|
||||||
|
var authorized = function() {
|
||||||
|
$rootScope.$broadcast('oauth:authorized', AccessToken.get());
|
||||||
|
scope.show = 'logged-in';
|
||||||
|
};
|
||||||
|
|
||||||
|
// set the oauth directive to the logged-out status
|
||||||
|
var loggedOut = function() {
|
||||||
|
$rootScope.$broadcast('oauth:loggedOut');
|
||||||
|
scope.show = 'logged-out';
|
||||||
|
};
|
||||||
|
|
||||||
|
// set the oauth directive to the denied status
|
||||||
|
var denied = function() {
|
||||||
|
scope.show = 'denied';
|
||||||
|
$rootScope.$broadcast('oauth:denied');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Updates the template at runtime
|
||||||
|
scope.$on('oauth:template:update', function(event, template) {
|
||||||
|
scope.template = template;
|
||||||
|
compile(scope);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hack to update the directive content on logout
|
||||||
|
// TODO think to a cleaner solution
|
||||||
|
scope.$on('$routeChangeSuccess', function () {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return definition;
|
||||||
|
}
|
||||||
|
]);
|
Loading…
Reference in New Issue