use spring-security protect ResourceServer

栏目: 后端 · 发布时间: 8年前

内容简介:use spring-security protect ResourceServer

來補一下怎麼保護 ResourceServer

套件依賴

build.gradle
buildscript {
    ext {
        springBootVersion = '1.5.2.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

jar {
    baseName = 'ps-account'
    version = '0.0.1-SNAPSHOT'
}

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('org.springframework.boot:spring-boot-starter-data-rest')
    compile('org.springframework.boot:spring-boot-starter-mail')
    compile('org.springframework.boot:spring-boot-starter-security')
    compile('org.springframework.boot:spring-boot-starter-web')
    compile 'org.springframework.security.oauth:spring-security-oauth2:2.0.12.RELEASE'
    compile 'org.springframework.security:spring-security-jwt:1.0.7.RELEASE'
    compile 'org.modelmapper:modelmapper:0.7.7'
    compile 'org.apache.commons:commons-lang3:3.5'
    compile 'mysql:mysql-connector-java:6.0.5'
    compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.6.1'
    compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.6.1'
    compile group: 'io.springfox', name: 'springfox-data-rest', version: '2.6.1'
    compileOnly('org.projectlombok:lombok')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

spring-security-oauth2 spring-security-jw 是我們需另外加進來的

我們會有一些基礎的帳號或是角色的物件

帳號

Account.java
@Data
@Entity
@EntityListeners(AuditingEntityListener.class) //加這行 CreatedBy 才會生效

public class Account {
    @Id
    //@GeneratedValue

    private String accountid;

    @NotNull
    @UniqueUsername(message = "Username already exists")
    @Size(min = 5, max = 255, message = "Username have to be grater than 8 characters")
    @Column(unique = true)
    private String username;

    private String email;

    @JsonIgnore
    @NotNull
    @Size(min = 8, max = 255, message = "Password have to be grater than 8 characters")
    private String password;

    @NotNull
    private boolean enabled = true;

    @NotNull
    private boolean credentialsexpired = false;

    @NotNull
    private boolean expired = false;

    @NotNull
    private boolean locked = false;

//    @Formula("(select * from Role r where r.roleid in (select roleid from account_role ar where ar.accountid = accountid ))")

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "AccountRole", joinColumns = @JoinColumn(name = "accountid", referencedColumnName = "accountid", insertable = false, updatable = false),
            inverseJoinColumns = @JoinColumn(name = "roleid", referencedColumnName = "roleid", insertable = false, updatable = false) )
    private List<Role> roles;

    @CreatedDate
    @Column(name = "createddate")
    private Date createddate;
    @CreatedBy
    @Column(name = "createdby")
    private String createdby;
    @LastModifiedDate
    @Column(name = "lastmodifieddate")
    private Date lastmodifieddate;
    @LastModifiedBy
    @Column(name = "lastmodifiedby")
    private String lastmodifiedby;
}

角色

Role.java
@Data
@Entity
@EntityListeners(AuditingEntityListener.class) //加這行 CreatedBy 才會生效

public class Role {
    @Id
    private String roleid;
    @NotNull
    private String code;
    @NotNull
    private String label;
    @CreatedDate
    @Column(name = "createddate")
    private Date createddate;
    @CreatedBy
    @Column(name = "createdby")
    private String createdby;
    @LastModifiedDate
    @Column(name = "lastmodifieddate")
    private Date lastmodifieddate;
    @LastModifiedBy
    @Column(name = "lastmodifiedby")
    private String lastmodifiedby;
}

配置 ResourceServer Security

OAuth2ResourceServerConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.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;

/**
 * Created by samchu on 2017/2/17.
 */
@Configuration
@EnableResourceServer
// prePostEnabled 很重要,可以讓你配置 @PreAuthorize("#oauth2.hasScope('account')") 在任何需要的方法上

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {

    // 這邊配置這服務的 resourceId ,當 jwt 中不含符合的 resourceId 則會拒絕操作

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("account");
        resources.tokenServices(tokenServices());
    }

    // 配置哪些資源可以不用檢驗 Token ,這邊是對 swagger 的檔案無條件存取 及忘記密碼 passwordforget,其他的都要

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.httpBasic().disable();
        http.authorizeRequests().antMatchers(
                "/swagger-ui.html",
                "/v2/api-docs/**",
                "/swagger-resources/**",
                "/webjars/**",
                "/api/v1/passwordforget"
        ).permitAll();
        // 或是也可以集中配置在這裡

        //.antMatchers(HttpMethod.GET, "/my").access("#oauth2.hasScope('my-resource.read')")

        http.authorizeRequests().anyRequest().fullyAuthenticated();
    }

    // 主要是配置 JWT 的密鑰

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("ASDFASFsdfsdfsdfsfadsf234asdfasfdas");
        return converter;
    }

    // 配置 JwtTokenStore

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    // 配置,使用預設的 DefaultTokenServices 就可以了,因為我們這邊只是做驗證而已

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        return defaultTokenServices;
    }
}

接下來就可以在 Service 這邊配置需要的權限

RoleService.java
import com.ps.dto.RoleDto;
import com.ps.model.Role;
import com.ps.repository.RoleRepository;
import org.apache.commons.lang3.RandomStringUtils;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import java.util.List;

/**
 * Created by samchu on 2017/2/17.
 */
@Service
public class RoleService {

    @Autowired
    private RoleRepository roleRepository;

    // 這邊需要有 role 或是 role.readonly 的操作範圍的人才可以讀取角色列表

    @PreAuthorize("#oauth2.hasScope('role') or #oauth2.hasScope('role.readonly')")
    public List<Role> listAll(){
        return roleRepository.findAll();
    }

    // 這邊是寫入角色,所以限定 role 的操作範圍才可以寫入

    @PreAuthorize("#oauth2.hasScope('role')")
    public Role create(RoleDto roleDto) {
        ModelMapper modelMapper = new ModelMapper();
        Role role = modelMapper.map(roleDto, Role.class);
        role.setRoleid(RandomStringUtils.randomAlphanumeric(10));
        roleRepository.save(role);
        return role;
    }
}

像是 hasScope('role') 或是 hasScope('role.readonly') 其實都是非常簡單好讀的敘述方式

那像用到 spring-boot-starter-data-rest 的功能,你其實可以像下面這樣設定

AccountRepository.java
import com.ps.model.Account;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.security.access.prepost.PreAuthorize;

/**
 * Created by samchu on 2017/2/9.
 */
@RepositoryRestResource
public interface AccountRepository extends JpaRepository<Account, String> {

    //@RestResource(path = "findByUsername")

    @Query("SELECT a FROM Account a WHERE a.username = :username")
    Account findByUsername(@Param("username") String username);

    // Prevents GET /accounts/:id

    @Override
    @RestResource(exported = true)
    @PreAuthorize("#oauth2.hasScope('account')")
    Account findOne(String id);

    // Prevents GET /account

    @Override
    @RestResource(exported = false)
    @PreAuthorize("#oauth2.hasScope('account')")
    Page<Account> findAll(Pageable pageable);

    // Prevents POST /account and PATCH /account/:id

    @Override
    @RestResource(exported = true)
    @PreAuthorize("#oauth2.hasScope('account')")
    Account save(Account s);

    // Prevents DELETE /account/:id

    @Override
    @RestResource(exported = false)
    @PreAuthorize("#oauth2.hasScope('account')")
    void delete(Account t);
}
  1. 透過 RestResource(exported = false) 來配置此方法是否供外部透過 Rest 操作
  2. 特過 PreAuthorize 一樣可以設定符合操作的 scope 範圍

RestResource 預設是 exported = true,所以有的時候你自己會卡到外部內部權限問題

舉例說外部 rest 操作必須要有 account 操作範圍,但是某些時候是系統內部要操作,這時候你就沒有 token 來檢驗

不過你可以另外設定個系統操作的 Repository 像下面,這樣一來這個 Class 內部所有的方法都不對外露出,就可以放心用啦

AccountPrivateRepository.java
@Repository
@RestResource(exported = false)
public interface AccountPrivateRepository extends JpaRepository<Account, String> {

    @Query("SELECT a FROM Account a WHERE a.username = :username")
    Account findByUsername(@Param("username") String username);
}

啟動程式

PsAccountApplication.java
@EnableJpaAuditing
//@EnableTransactionManagement

@SpringBootApplication
public class PsAccountApplication {

    public static void main(String[] args) {
        SpringApplication.run(PsAccountApplication.class, args);
    }
}

透過標準的 OAuth 跟 JWT 是不是省下很多自己開發的力氣呢?

使用 OAuth 不論是自用或開放兩相宜XD,使用 JWT 簡化驗證架構跟流程

雖然學習是有成本的,但是比起自己做一套授權系統,應該還是划算的很多

把力氣留在你的商業平台吧,這種基礎建設就盡量使用開源以及標準吧

← spring-retry 使用方式


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Tagging

Tagging

Gene Smith / New Riders / 2007-12-27 / GBP 28.99

Tagging is fast becoming one of the primary ways people organize and manage digital information. Tagging complements traditional organizational tools like folders and search on users desktops as well ......一起来看看 《Tagging》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试