美文网首页
Spring Boot Rest 接口添加OAuth token

Spring Boot Rest 接口添加OAuth token

作者: ilaoke | 来源:发表于2017-12-27 18:22 被阅读1039次

    https://projects.spring.io/spring-security-oauth/docs/oauth2.html
    https://github.com/royclarkson/spring-rest-service-oauth
    https://github.com/spring-projects/spring-security-oauth/blob/master/tests/annotation/jdbc/src/main/resources/schema.sql
    https://github.com/spring-projects/spring-boot/issues/8478

    适应场景

    为Spring Restful接口添加OAuth2 token机制,适用手机APP的后台,以及前后端分离下的后台接口。

    添加依赖

    创建Spring Boot项目(1.5.9.RELEASE),POM中添加:

            <dependency>
                <groupId>org.springframework.security.oauth</groupId>
                <artifactId>spring-security-oauth2</artifactId>
            </dependency>
    

    token可以保存在内存、数据库、Redis中,此处我们选择保存到MySql数据库,DDL如下:

    CREATE TABLE `oauth_access_token` (
      `token_id` varchar(255) DEFAULT NULL,
      `token` blob,
      `authentication_id` varchar(255) NOT NULL,
      `user_name` varchar(255) DEFAULT NULL,
      `client_id` varchar(255) DEFAULT NULL,
      `authentication` blob,
      `refresh_token` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`authentication_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    CREATE TABLE `oauth_refresh_token` (
      `token_id` varchar(255) NOT NULL,
      `token` blob,
      `authentication` blob,
      PRIMARY KEY (`token_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    client也可以放到数据库中,此处我们将client放在内存中,如果需要保存到数据库,DDL可以参考这里

    配置

    1. OAuth2相关配置
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.core.userdetails.UserDetailsService;
    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.configuration.EnableResourceServer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
    import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
    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.JdbcTokenStore;
    
    import javax.sql.DataSource;
    
    /**
     * OAuth2相关配置:使用默认的DefaultTokenServices,token保存到数据库,以及token超时时间设置
     */
    @Configuration
    public class OAuth2ServerConfiguration {
    
        private static final String RESOURCE_ID = "RS_DEMO";
        private static final String CLIENT_ID = "APP";
    
        @Configuration
        @EnableResourceServer
        protected static class ResourceServerConfiguration extends
                ResourceServerConfigurerAdapter {
    
            @Override
            public void configure(ResourceServerSecurityConfigurer resources) {
                resources.resourceId(RESOURCE_ID);
            }
    
            @Override
            public void configure(HttpSecurity http) throws Exception {
                http.authorizeRequests().anyRequest().authenticated();
            }
    
        }
    
        @Configuration
        @EnableAuthorizationServer
        protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    
            @Autowired
            @Qualifier("dataSource")
            private DataSource dataSource;
    
            @Autowired
            @Qualifier("authenticationManagerBean")
            private AuthenticationManager authenticationManager;
    
            @Autowired
            @Qualifier("userDetailsService")
            private UserDetailsService userDetailsService;
    
            @Bean
            public TokenStore tokenStore() {
                return new JdbcTokenStore(dataSource);
            }
    
    
            @Override
            public void configure(AuthorizationServerEndpointsConfigurer endpoints)
                    throws Exception {
                // @formatter:off
                endpoints
                    .tokenStore(tokenStore())
                    .authenticationManager(this.authenticationManager)
                    .userDetailsService(userDetailsService);
                // @formatter:on
            }
    
            /**
             * 1. client信息放在内存中,ID和Secret设置为固定值
             * 2. 设置access_token和refresh_token的超时时间
             */
            @Override
            public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
                // @formatter:off
                clients
                    .inMemory()
                        .withClient(CLIENT_ID)
                            .authorizedGrantTypes("password", "refresh_token")
                            .scopes("read", "write", "trust")
                            .resourceIds(RESOURCE_ID)
                            .secret("secret")
                            .accessTokenValiditySeconds(60*60*12)
                            .refreshTokenValiditySeconds(60*60*24*30);
                // @formatter:on
            }
    
            @Bean
            @Primary
            public DefaultTokenServices tokenServices() {
                DefaultTokenServices tokenServices = new DefaultTokenServices();
                tokenServices.setSupportRefreshToken(true);
                tokenServices.setTokenStore(tokenStore());
                return tokenServices;
            }
    
        }
    }
    
    
    1. WEB Security配置,此处配置所有/auth/*的URL需要access_token,/anon/*的URL不需要token也能访问。如果需要跨域,Chrome浏览器会先发送OPTIONS方法来检查接口是否支持跨域访问,所以这里也添加了对OPTIONS请求不验证TOKEN。
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.authentication.AuthenticationManager;
    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.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    
    /**
     * 权限相关配置:注册密码算法,设置UserDetailsService
     */
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        @Autowired
        @Qualifier("userDetailsService")
        private UserDetailsService userDetailsService;
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable();
            http.authorizeRequests().antMatchers("/auth/*").authenticated();
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            // 允许OPTIONS方法访问,不做auth验证,因为的跨域开发是,Chrome浏览器会先发送OPTIONS再真正执行GET POST
            // 如果正式发布时在同一个域下,请去掉`and().ignoring().antMatchers(HttpMethod.OPTIONS)`
            web.ignoring().antMatchers("/anon/**").and().ignoring().antMatchers(HttpMethod.OPTIONS);
        }
    
        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
    }
    
    1. UserSerice,需要实现org.springframework.security.core.userdetails.UserDetailsService
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    
    @Service("userDetailsService")
    public class UserService implements UserDetailsService{
        @Autowired
        UserMapper userMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User user =  userMapper.selectByIfkCode(username);
            if(user == null){
                throw new UsernameNotFoundException("用户不存在!");
            }
            return new CustomUser(user);
        }
    }
    

    实现Spring的UserDetails

    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    /**
     * UserDetailsService返回当前对象,该对象包含User实体,User必须实现Serializable
     */
    public class CustomUser extends User implements UserDetails {
        private User user = null;
    
        public CustomUser(User user) {
            super(user);
            this.user = user;
        }
    
        public User getUser(){
            return user;
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            // 此处可以设置用户的具体权限,这里我们返回空集合
            return Collections.emptySet();
        }
    
        @Override
        public String getUsername() {
            return super.getUserName();
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return true;
        }
    }
    

    User实体

    public class User implements Serializable{
        private static final long serialVersionUID = -7810168718373868640L;
    
        public User() {}
    
        public User(User user) {
            super();
            this.userId = user.getUserId();
            this.userName = user.getUserName();
            this.mobile = user.getMobile();
            this.password = user.getPassword();
            this.email = user.getEmail();
        }
        private Long userId;
        private String password;
        private String userName;
        private String mobile;
        private String email;
    
        // .... set get...
    }
    

    Controller

    通过@AuthenticationPrincipal获取用户信息,如果无法获取用户信息,在application.properties中添加:

    security.oauth2.resource.filter-order=3
    
    @Controller
    public class StudentController {
    
        @RequestMapping(value = "/anon/student2", method = RequestMethod.GET)
        @ResponseBody
        public Student student2(Long orgId) {
            Student stu = new Student();
            stu.setOrgId(orgId);
            return stu;
        }
    
        @RequestMapping("/auth/student3")
        @ResponseBody
        public Map<String, Object> student3(@AuthenticationPrincipal User user) {
            Map map = new HashMap();
            map.put("s1", "s1");
            map.put("s2", "s2");
            if (user != null) {
                map.put("s3", user.getPassword());
            }
            return map;
        }
    }
    

    访问接口

    http://localhost:8088/oauth/token?username=T0000053&password=111111&grant_type=password&scope=read%20write%20trust
    

    获取access_token和refresh_token,Headers中的Authorization是clientId:secret做Base64的结果

    access_token.png

    刷新token

    http://localhost:8088/oauth/token?grant_type=refresh_token&scope=read%20write%20trust&refresh_token=df31b988-ccb3-42ae-a61b-cae5ad44e939
    
    refresh_token.png

    访问受保护接口


    无token

    Header添加access_token,Authorization:Bearer 69ffcef9-023a-4685-ace9-faf3dc5cef17

    有token

    相关文章

      网友评论

          本文标题:Spring Boot Rest 接口添加OAuth token

          本文链接:https://www.haomeiwen.com/subject/oqfsuttx.html