zoukankan      html  css  js  c++  java
  • trackr: An AngularJS app with a Java 8 backend – Part IV 实践篇

    REST API对于前后端或后端与后端之间通讯是一个好的接口,而单页应用Single Page Applications (SPA)非常流行. 我们依然以trackr为案例,这是一个跟踪工作时间 请假 差旅花费 发票等管理系统。前端使用AngularJS,后端是基于Java 8 与Spring 4,API是通过OAuth2加密.

    该项目已开源,地址戳这里,后端代码下载:here (backend) ,前端下载: here (frontend).

    1. Gradle和Spring Boot

    基于Spring Boot的基本Gradle配置如下:

    apply plugin: 'java'
    apply plugin: 'spring-boot'
    jar {
    baseName = 'jaxenter-example'
    version = '1.0'
    }
    dependencies {
    compile("org.springframework.boot:spring-boot-starter")
    compile("org.springframework.boot:spring-boot-starter-logging")
    }

    下面是Spring Boot的基本代码,主要魔力是 @EnableAutoConfiguration

    @Configuration
    @EnableAutoConfiguration
    @ComponentScan
    public class Application implements CommandLineRunner {
    private Logger logger = LoggerFactory.getLogger(Application.class);
    @Autowired
    private SomeService someService;
    @Override
    public void run(String... args) throws Exception {
    String foo = someService.foo();
    logger.info("SomeService returned {}", foo);
    }
    public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
    }
    }

    Spring 服务的基本代码如下:

    @Service
    public class SomeService {
    private Logger logger = LoggerFactory.getLogger(SomeService.class);
    public String foo() {
    logger.debug("Foo has been called");
    return "bar";
    }
    }

    如果我们增加@EnableScheduling,那么以@Scheduled的方法将定期自动执行。

    好了,我们通过Gradle可以打包得到一个Jar包,将其部署到Docker等容器中作为微服务。

    2.增加持久层和REST服务

    我们如果将HSQL的驱动包加入系统Classpath,Spring Boot会自动发现它加载,同时我们需要使用Spring Data,在Build.gradle中加入:

    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    runtime("org.hsqldb:hsqldb")
    compile("org.projectlombok:lombok:1.14.8")

    编写下面仓储类使用Spring Data:

    @Configuration
    @EnableJpaRepositories
    public class PersistenceConfiguration extends JpaRepositoryConfigExtension {
    // I added some code to put two persons into the database here.
    }

    因为我们之前已经激活Spring进行组件自动扫描,因此这个类将会被Spring自动发现加载,下面我们编写实体类:

    @Entity
    @Data
    public class Person {
    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    }
    public interface PersonRepository extends JpaRepository<Person, Long> {
    List<Person> findByFirstNameLike(String firstName);
    }

    现在我们需要访问数据表persons,能够根据第一个名称查询,其他基本方法Spring Data JPA 都会提供.

    现在需要加入一些依赖,改变仓储一行代码以便实现:

    1. 通过HTTP实现person的增删改
    2. 分页查询persons
    3. 用户查找

    gradle一行加入如下:

    compile("org.springframework.boot:spring-boot-starter-data-rest")

    PersonRepository 需要一个新的注解:

    List<Person> findByFirstNameLike(@Param("firstName") String firstName);

    如果启动我们的应用,下面通过curl访问API应该可以工作:

    curl localhost:8080
    curl localhost:8080/persons
    curl -X POST -H "Content-Type: application/json" -d "{"firstName": "John"}"
    localhost:8080/persons
    curl localhost:8080/persons/search/findByFirstNameLike?firstName=J%25
    curl -X PUT localhost:8080/persons/1 -d "{"firstName": "Jane"}" -H "Content-Type:
    application/json"
    curl -X DELETE localhost:8080/persons/1

    现在REST是公开的任何人可以访问。下面加入安全。

    3.用Spring scecurity加密REST

    现在为了支持Spring security,在gradle配置加入:

    compile("org.springframework.boot:spring-boot-starter-security")

    启动应用后,在日志中看到:

    Using default security password: ed727172-deff-4789-8f79-e743e5342356

    此时用户名是user,上面是密码,那么我们可以使用这对用户名密码访问REST:

    curl user:ed727172-deff-4789-8f79-e743e5342356@localhost:8080/persons

    当然在真实项目中,我们需要多用户和多角色。

    比如我们加入admin角色,只有admin才能查询所有人和查找他们。首先我们要加入自己的安全配置:

    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    @EnableWebSecurity
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private FakeUserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService);
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().fullyAuthenticated();
    http.httpBasic();
    http.csrf().disable();
    }
    }

    下面的服务将用户名映射到我们自己数据表的人名:

    @Service
    public class FakeUserDetailsService implements UserDetailsService {
    @Autowired
    private PersonRepository personRepository;
    @Override
    public UserDetails loadUserByUsername(String username) throws
    UsernameNotFoundException {
    Person person = personRepository.findByFirstNameEquals(username);
    if (person == null) {
    throw new UsernameNotFoundException("Username " + username + " not
    found");
    }
    return new User(username, "password", getGrantedAuthorities(username));
    }
    private Collection<? extends GrantedAuthority> getGrantedAuthorities(String
    username) {
    Collection<? extends GrantedAuthority> authorities;
    if (username.equals("John")) {
    authorities = asList(() -> "ROLE_ADMIN", () -> "ROLE_BASIC");
    } else {
    authorities = asList(() -> "ROLE_BASIC");
    }
    return authorities;
    }
    }

    这里你会看到Java 8的lambda的使用。

    最后我们改变Spring Data使用我们自己的安全定义:

    @Override
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    Page<Person> findAll(Pageable pageable);
    @Override
    @PostAuthorize("returnObject.firstName == principal.username or
    hasRole('ROLE_ADMIN')")
    Person findOne(Long aLong);
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    List<Person> findByFirstNameLike(@Param("firstName") String firstName);

    这里定义了admin可以查询所有人和查找某个人。

    我们重启动该应用后,可以测试一下:

    % curl Mary:password@localhost:8080/persons/1
    {"timestamp":1414951322459,"status":403,"error":"Forbidden","exception":"org.springfra
    mework.security.access.AccessDeniedException","message":"Access is
    denied","path":"/persons/1"}

    如果我们使用John访问marry的账户,会得到403错误。

    你会注意到缺省安全码还是存在,但是已经失效。

    以上只有查询GET,如果需要PUT POST,我们也需要增加安全检查:

    @Component
    @RepositoryEventHandler(Person.class)
    public class PersonEventHandler {
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @HandleBeforeSave
    public void checkPUTAuthority(Person person) {
    // only security check
    }
    }

    现在创建和删除都有安全检查了。

    4.增加OAuth

    我们需要使用Spring Security OAuth.实现OAuth2。下面我们首先编制一个OAuth客户端:

    @Configuration
    @EnableAuthorizationServer
    public class OAuthConfiguration extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private DataSource dataSource;
    @Bean
    public TokenStore tokenStore() {
    return new JdbcTokenStore(dataSource);
    }
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws
    Exception {
    endpoints.tokenStore(tokenStore());
    }
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
    .withClient("curl")
    .authorities("ROLE_ADMIN")
    .resourceIds("jaxenter")
    .scopes("read", "write")
    .authorizedGrantTypes("client_credentials")
    .secret("password")
    .and()
    .withClient("web")
    .redirectUris("http://github.com/techdev-solutions/")
    .resourceIds("jaxenter")
    .scopes("read")
    .authorizedGrantTypes("implicit");
    }
    }

    这是从 /oauth/token获得token,这个客户端将用户发往/oauth/authorize进行授权,授权用户可以访问服务器的资源,这些端点和Web页面都包含在Spring Security OAuth中。

    在我们的person能够登录之前加入如下配置:

    @Configuration
    @EnableWebSecurity
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
    .withUser("John").roles("ADMIN").password("password")
    .and()
    .withUser("Mary").roles("BASIC").password("password");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().antMatchers("/**").authenticated()
    .and().httpBasic().realmName("OAuth Server");
    }
    }

    现在授权服务器已经完成,下面现在让我们的REST API知道它已经是一个资源服务器,使用同样的将数据库token作为授权服务器。

    @Configuration
    @EnableResourceServer
    public class OAuthConfiguration extends ResourceServerConfigurerAdapter {
    @Value("${oauth_db}")
    private String oauthDbJdbc;
    @Bean
    public TokenStore tokenStore() {
    DataSource tokenDataSource =
    DataSourceBuilder.create().driverClassName("org.sqlite.JDBC").url(oauthDbJdbc).build()
    ;
    return new JdbcTokenStore(tokenDataSource);
    }
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception
    {
    resources.resourceId("jaxenter")
    .tokenStore(tokenStore());

    这个配置将替代老的HttpSecurity,老的HttpSecurity失效。

    现在应用必须重新启动,我们配置授权服务器运行在8081端口,如果有必要初始化token数据库,当授权服务器已经开始运行,我们能使用下面基本授权方式请求一个token:

    curl curl:password@localhost:8081/oauth/token?grant_type=client_credentials

    作为响应,我们得到一个token,如下面方式使用:

    curl -H "Authorization: Bearer $token" localhost:8080

    我们给定cURL客户端以admin角色和读写范围,这样一切就OK了。

    下一步,在web客户端浏览器中,我们访问URL http://localhost:8081/oauth/authorize?client_id=web&response_type=token

    作为John登入,得到一个授权页面,如果我们有一个实际已经配置的web客户端,那么就会返回URL。

  • 相关阅读:
    IO学习BufferedWriter 规格严格
    Finalization 规格严格
    linux下查看主板内存槽与内存信息 规格严格
    调试JavaScript/VB Script脚本程序(Wscript篇) 规格严格
    ORA01688:unable to extend table name。name partition NAME by NUM in tablespace NAME 规格严格
    Hidden Java 7 Features – SecondaryLoop 规格严格
    Web应用服务器监控 规格严格
    NetBeans 时事通讯(刊号 # 12 Jun 16, 2008)
    NetBeans 时事通讯(刊号 # 12 Jun 16, 2008)
    Win32 DLL的一个调试心得
  • 原文地址:https://www.cnblogs.com/mignet/p/trackr-an-angularjs-app-with-a-java-8-backend-part-iv.html
Copyright © 2011-2022 走看看