入门实战: Spring Security

我的未来我决定 提交于 2020-02-27 02:37:36

认证授权

OAuth 2.0 Authorization Server

  1. 新建项目
  • Artifact: auth-server
  • Dependencies:
<dependencies>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>  
    </dependency>  
  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-data-jpa</artifactId>  
    </dependency>  
  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-security</artifactId>  
    </dependency>  
  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-web</artifactId>  
    </dependency>  
  
    <dependency>  
        <groupId>org.springframework.security.oauth.boot</groupId>  
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>  
        <version>2.2.0.M3</version>  
    </dependency>  
  
    <dependency>  
        <groupId>com.nimbusds</groupId>  
        <artifactId>nimbus-jose-jwt</artifactId>  
        <version>7.0.1</version>  
    </dependency>  
  
    <dependency>  
        <groupId>org.springframework.security</groupId>  
        <artifactId>spring-security-jwt</artifactId>  
        <version>1.1.0.RELEASE</version>  
    </dependency>  
  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-web</artifactId>  
    </dependency>  
  
    <dependency>  
        <groupId>mysql</groupId>  
        <artifactId>mysql-connector-java</artifactId>  
    </dependency>  
</dependencies>
  • resources/application.yml
spring:  
 datasource: 
   url: jdbc:mysql://localhost:3306/first_db?useSSL=false  
    username: root  
    password: jjjjjj  
    driver-class-name: com.mysql.cj.jdbc.Driver  
    initialization-mode: always  
    continue-on-error: true  
 jpa: 
   show-sql: true 
   hibernate: 
     ddl-auto: update  
 security:  
   oauth2: 
     resourceserver: 
      jwt: 
        public-key-location: classpath:public.txt
  • resources/schema.sql
create table if not exists oauth_client_details (  
  client_id VARCHAR(255) PRIMARY KEY,  
  resource_ids VARCHAR(255),  
  client_secret VARCHAR(255),  
  scope VARCHAR(255),  
  authorized_grant_types VARCHAR(255),  
  web_server_redirect_uri VARCHAR(255),  
  authorities VARCHAR(255),  
  access_token_validity INTEGER,  
  refresh_token_validity INTEGER,  
  additional_information VARCHAR(4096),  
  autoapprove varchar(255)  
);
  • resources/data.sql
INSERT INTO oauth_client_details (client_id, client_secret, scope, authorized_grant_types, autoapprove)  
values ('postman',  
        '$2a$10$KCroi.THbmXdKXOgBud1zOzdmHrfpNxSytd/o5ZQLhCTzFXib1p66',  
        'any',  
        'password,authorization_code,refresh_token',  
        true);  
INSERT INTO oauth\_client\_details (client_id, client_secret, authorized_grant_types, autoapprove)  
values ('app',  
        '$2a$10$1vUvCP9fzPeXIRzoOaPROuIRvq2nrh7iauWLIa371qZSmaP0p6ave',  
        'any', 
        'implicit',  
        true);
  • resources/keystore.jks
keytool -list -rfc --keystore keystore.jks | openssl x509 -inform pem -pubkey -noout
  • resources/public.txt
@Bean  
CommandLineRunner publicKey(KeyPair keyPair) {  
    return args -> {  
        System.out.println(Base64.encodeBase64String(keyPair.getPublic().getEncoded()));  
    };  
}
  • entity/SysAuthority
@Data  
@AllArgsConstructor  
@NoArgsConstructor  
@Entity  
public class SysAuthority {  
  
    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long id;  
  
    private String name;  
  
    private String value;  
  
    public SysAuthority(String name, String value) {  
        this.name = name;  
        this.value = value;  
    }  
}
  • entity/SysRole
@Data  
@AllArgsConstructor  
@NoArgsConstructor  
@Entity  
public class SysRole {  
  
    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long id;  
  
    private String name;  
  
    @ManyToMany(targetEntity = SysAuthority.class)  
    private Set<SysAuthority> authorities;  
  
    public SysRole(String name, Set<SysAuthority> authorities) {  
        this.name = name;  
        this.authorities = authorities;  
    }  
}
  • entity/SysUser
@Data  
@AllArgsConstructor  
@NoArgsConstructor  
@Entity  
public class SysUser implements UserDetails {  
  
    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long id;  
  
    @Column(unique = true)  
    @NotNull  
    private String username;  
  
    private String password;  
  
    private String realName;  
  
    private Boolean enable;  
  
    @ManyToMany(targetEntity = SysRole.class, fetch = FetchType.EAGER)  
    private Set<SysRole> roles;  
  
    public SysUser(String username, String password, String realName, Boolean enable, Set<SysRole> roles) {  
        this.username = username;  
        this.password = password;  
        this.realName = realName;  
        this.enable = enable;  
        this.roles = roles;  
    }  
  
    @Override  
    public Collection<? extends GrantedAuthority> getAuthorities() {  
        Collection<GrantedAuthority> authorities = new HashSet<>();  
        roles.forEach(role -> {  
            role.getAuthorities().forEach(authority -> {  
                authorities.add(new SimpleGrantedAuthority(authority.getValue()));  
            });  
        });  
        return authorities;  
    }  
  
    @Override  
    public String getPassword() {  
        return this.password;  
    }  
  
    @Override  
    public String getUsername() {  
        return this.username;  
    }  
  
    @Override  
    public boolean isAccountNonExpired() {  
        return true;  
    }  
  
    @Override  
    public boolean isAccountNonLocked() {  
        return true;  
    }  
  
    @Override  
    public boolean isCredentialsNonExpired() {  
        return true;  
    }  
  
    @Override  
    public boolean isEnabled() {  
        return this.enable;  
    }  
}
  • repository/SysAuthorityRepository
public interface SysAuthorityRepository extends JpaRepository<SysAuthority, Long> {  
}
  • repository/SysRoleRepository
public interface SysRoleRepository extends JpaRepository<SysRole, Long> {  
}
  • repository/SysUserRepository
public interface SysUserRepository extends JpaRepository<SysUser, Long> {  
    Optional<SysUser> findByUsername(String username);  
}
  • AuthServerApplication
@SpringBootApplication  
public class AuthServerApplication {  
  
    public static void main(String[] args) {  
        SpringApplication.run(AuthServerApplication.class, args);  
    }  
  
    //@Bean  
    CommandLineRunner init(SysUserRepository sysUserRepository,  
                           SysRoleRepository sysRoleRepository,  
                           SysAuthorityRepository sysAuthorityRepository,  
                           PasswordEncoder passwordEncoder){  
        return args -> {  
            SysAuthority authority1 = sysAuthorityRepository.save(new SysAuthority("userCan1","userCan1"));  
            SysAuthority authority2 = sysAuthorityRepository.save(new SysAuthority("userCan2","userCan2"));  
            SysAuthority authority3 = sysAuthorityRepository.save(new SysAuthority("adminCan1","adminCan1"));  
            SysAuthority authority4 = sysAuthorityRepository.save(new SysAuthority("adminCan2","adminCan2"));  
  
            SysRole role1 = sysRoleRepository.save(new SysRole("普通用户",  
                    Stream.of(authority1,authority2).collect(Collectors.toSet())));  
            SysRole role2 = sysRoleRepository.save(new SysRole("管理员",  
                    Stream.of(authority1,authority2,authority3,authority4).collect(Collectors.toSet())));  
  
            SysUser user1 = sysUserRepository.save(new SysUser("wyf",  
                    passwordEncoder.encode("111111"),  
                    "wangyunfei",  
                    true,  
                    Stream.of(role1).collect(Collectors.toSet())));  
  
            SysUser user2 = sysUserRepository.save(new SysUser("admin",  
                    passwordEncoder.encode("admin"),  
                    "administrator",  
                    true,  
                    Stream.of(role2).collect(Collectors.toSet())));  
  
        };  
    }  
}
  1. 安全配置
  • security/CusotmUserDetailsService
public class CusotmUserDetailsService implements UserDetailsService {  
  
    SysUserRepository sysUserRepository;  
  
    public CusotmUserDetailsService(SysUserRepository sysUserRepository) {  
        this.sysUserRepository = sysUserRepository;  
    }  
  
    @Override  
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {  
        Optional<SysUser> sysUserOptional = sysUserRepository.findByUsername(username);  
        return sysUserOptional  
                .orElseThrow(() -> new UsernameNotFoundException("Username not found!"));  
    }  
}
  • config/WebSecurityConfig
@Configuration  
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {  
  
    @Autowired  
    SysUserRepository sysUserRepository;  
  
    @Bean  
    protected UserDetailsService userDetailsService() {  
        return new CusotmUserDetailsService(sysUserRepository);  
    }  
  
    @Bean  
    PasswordEncoder passwordEncoder() {  
        return new BCryptPasswordEncoder();  
    }  
  
    @Bean  
    @Override  
    protected AuthenticationManager authenticationManager() throws Exception {  
        return super.authenticationManager();  
    }  
  
    @Override  
    protected void configure(HttpSecurity http) throws Exception {  
        http.authorizeRequests()  
                .anyRequest().authenticated();  
    }  
}
  1. JWT 配置
  • application.yml
security:  
 oauth2: 
   authorization:
     jwt: 
        key-store: classpath:keystore.jks  
        key-store-password: pass1234  
        key-alias: wisely  
        key-password: pass1234  
      tokenKeyAccess: permitAll()  
      checkTokenAccess: isAuthenticated()  
      realm: wisely
  1. 授权服务配置
  • config/AuthServerConfig
@EnableAuthorizationServer  
@Configuration  
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {  
  
    private final PasswordEncoder passwordEncoder;  
    private final DataSource dataSource;  
    private final AuthenticationManager authenticationManager;  
    private final UserDetailsService userDetailsService;  
    private final TokenStore tokenStore;  
    private final AccessTokenConverter accessTokenConverter;  
    private final AuthorizationServerProperties properties;  
  
    public AuthServerConfig(PasswordEncoder passwordEncoder,  
                            DataSource dataSource,  
                            AuthenticationManager authenticationManager,  
                            UserDetailsService userDetailsService,  
                            TokenStore tokenStore,  
                            AccessTokenConverter accessTokenConverter,  
                            AuthorizationServerProperties properties) {  
        this.passwordEncoder = passwordEncoder;  
        this.dataSource = dataSource;  
        this.authenticationManager = authenticationManager;  
        this.userDetailsService = userDetailsService;  
        this.tokenStore = tokenStore;  
        this.accessTokenConverter = accessTokenConverter;  
        this.properties = properties;  
    }  
  
  /**  
  * 配置客户端 
  * @param clients  
  * @throws Exception  
  */  
  @Override  
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {  
        clients  
                .jdbc(this.dataSource)  
                .passwordEncoder(passwordEncoder);  
//  
//        JdbcClientDetailsService detailsService = new JdbcClientDetailsService(this.dataSource);  
//        detailsService.setPasswordEncoder(passwordEncoder);  
//        clients.withClientDetails(detailsService);  
  
//    clients  
//        .inMemory()  
//            .withClient("postman")  
//            .secret(passwordEncoder.encode("postman"))  
//            .scopes("any")  
//            .authorizedGrantTypes("password", "authorization\_code", "refresh\_token")  
//        .and()  
//            .withClient("app")  
//            .secret(passwordEncoder.encode("app"))  
//            .scopes("any")  
//            .authorizedGrantTypes("password", "authorization\_code", "refresh\_token");  
  }  
  
  /**  
 * 配置端点非安全属性 
 * @param endpoints  
 * @throws Exception  
 */  
  @Override  
  public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {  
        endpoints.accessTokenConverter(this.accessTokenConverter);  
        endpoints.tokenStore(this.tokenStore);  
        endpoints.authenticationManager(this.authenticationManager);  
        endpoints.userDetailsService(userDetailsService);  
    }  
  
    @Override  
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {  
        security.checkTokenAccess(this.properties.getCheckTokenAccess());  
        security.tokenKeyAccess(this.properties.getTokenKeyAccess());  
        security.realm(this.properties.getRealm());  
    }  
}
  1. Authorization Server 的端点
  • TokenEndpoint:/oauth/token,按照 OAuth 2.0 规范请求 Token。
  • TokenKeyEndpoint:/oauth/token_key,提供 JWT 编码的 Token。
  • CheckTokenEndpoint:/oauth/check_token,解码客户端的访问令牌用来检查和确认生成的 Token。
  • AuthorizationEndpoint:/oauth/authorize,遵循 OAuth 2.0 规范的授权实现。
  1. 获取 Token

OAuth 2.0 Resource Server

  1. 新建应用
  • Artifact: resource-server
  • Dependencies:
<dependencies>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>  
    </dependency>
	
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-security</artifactId>  
    </dependency>
	
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-web</artifactId>  
    </dependency>
</dependencies>
  • 公钥配置 application.yml
spring:  
 security: 
   oauth2: 
     resourceserver: 
       jwt: 
         public-key-location: classpath: public.txt    
  
server:  
 port: 8082
  1. 获取资源服务权限
  • config/WebSecurityConfig
@EnableGlobalMethodSecurity(prePostEnabled = true)  
@EnableWebSecurity  
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {  
    @Override  
    protected void configure(HttpSecurity http) throws Exception {  
        http.authorizeRequests()  
                .anyRequest().authenticated()  
                .and()  
                .oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwt -> {  
                    Collection<SimpleGrantedAuthority> authorities = ((Collection<String>) jwt.getClaims().get("authorities")).stream()  
                            .map(SimpleGrantedAuthority::new)  
                            .collect(Collectors.toSet());  
                    return new JwtAuthenticationToken(jwt, authorities);  
        });  
    }  
  
}
  1. 测试控制器
  • controller/SecurityController
@RestController  
public class SecurityController {  
  
    @GetMapping("/userCan1")  
    @PreAuthorize("hasAuthority('userCan1')")  
    public Jwt userCan1(@AuthenticationPrincipal Jwt jwt){  
        return jwt;  
    }  
  
    @GetMapping("/userCan2")  
    @PreAuthorize("hasAuthority('userCan2')")  
    public Jwt userCan2(@AuthenticationPrincipal Jwt jwt){  
        return jwt;  
    }  
  
    @GetMapping("/adminCan1")  
    @PreAuthorize("hasAuthority('adminCan1')")  
    public Jwt adminCan1(@AuthenticationPrincipal Jwt jwt){  
        return jwt;  
    }  
  
    @GetMapping("/adminCan2")  
    @PreAuthorize("hasAuthority('adminCan2')")  
    public Jwt adminCan2(@AuthenticationPrincipal Jwt jwt){  
        return jwt;  
    }  
  
}
  1. 验证
    分别用 wyf 和 admin 请求token,复制 wyf 和 admin 的 access_token 请求 http://localhost:8082/adminCan1

Gateway

客户端调用

  1. 获取 Access Token
  2. 访问 Resource Server

微服务互调

  1. 为 resource-server 添加方法,用于远程调用
  2. 新建调用微服务
  3. 外部配置
  4. 开启支持
  5. Security 配置
  6. Feign 客户端
  7. 控制器
  8. 验证
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!