• SpringBoot的Security和OAuth2的使用


    创建项目

    先创建一个spring项目。

    然后编写pom文件如下,引入spring-boot-starter-security,我这里使用的spring boot是2.4.2,这里使用使用spring-boot-dependencies,在这里就能找到对应的security的包。

    
    <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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
        <groupId>com.examplegroupId>
        <artifactId>app-kiba-securityartifactId>
        <version>0.0.1-SNAPSHOTversion>
        <name>app-kiba-securityname>
        <description>app-kiba-securitydescription>
        <properties>
            <java.version>1.8java.version>
            <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
            <spring-boot.version>2.4.2spring-boot.version>
        properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-securityartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
    
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
    
    
        dependencies>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-dependenciesartifactId>
                    <version>${spring-boot.version}version>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
            dependencies>
        dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.pluginsgroupId>
                    <artifactId>maven-compiler-pluginartifactId>
                    <version>3.8.1version>
                    <configuration>
                        <source>1.8source>
                        <target>1.8target>
                        <encoding>UTF-8encoding>
                    configuration>
                plugin>
                <plugin>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-maven-pluginartifactId>
                    <version>${spring-boot.version}version>
                    <configuration>
                        <mainClass>com.kiba.appkibasecurity.AppKibaSecurityApplicationmainClass>
                        <skip>trueskip>
                    configuration>
                    <executions>
                        <execution>
                            <id>repackageid>
                            <goals>
                                <goal>repackagegoal>
                            goals>
                        execution>
                    executions>
                plugin>
            plugins>
        build>
    
    project>
    
    

    然后访问创建项目时默认生成的接口:http://127.0.0.1:8080/user/123/roles/222,得到如下界面。

    image

    这是相当于,在我们的接口请求的前面做了一个拦截,类似filter,拦截后,跳转到了一个界面,让我们输入账号密码。这里,我由于没有设置账号密码,所以登录不进去。

    设置访问一

    下面设置一个账号密码,并且设置hello接口可以直接访问,设置很简单,就是注入两个bean,InMemoryUserDetailsManager和WebSecurityCustomizer,代码如下:

    @Configuration
    public class SecurityConfig   {
        /**
         * 注册用户,这里用户是在内存中的
         *  {noop}表示“无操作”(No Operation)密码编码。
         * @return
         */
        @Bean
        UserDetailsService userDetailsService() {
            InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
            users.createUser(User.withUsername("kiba").password("{noop}123").roles("admin").build()); 
            return users;
        }
    
        /**
         * 让hello可以不用登录,就可以直接访问,例如:http://127.0.0.1:8080/hello?name=kiba就可以直接访问
         * @return
         */
        @Bean
        WebSecurityCustomizer webSecurityCustomizer() {
            return new WebSecurityCustomizer() {
                @Override
                public void customize(WebSecurity web) {
                    web.ignoring().antMatchers("/hello");
                }
            };
        }
    
    }
    
    

    现在我们访问http://127.0.0.1:8080/user/123/roles/222,进入到登录页面,输入kiba/123就可以查看接口执行的结果了。

    http://127.0.0.1:8080/hello?name=kiba就无需登录,可以直接访问。

    登录一次,其他接口就可以自由访问了

    控制请求

    现在,增加一个类SecurityAdapter,继承自WebSecurityConfigurerAdapter。然后重写他的configure方法

    @Configuration
    @AllArgsConstructor
    public class SecurityAdapter extends WebSecurityConfigurerAdapter {
    
        /**
         * authenticated():用户需要通过用户名/密码登录,记住我功能也可以(remember-me)。
         * fullyAuthenticated()用户需要通过用户名/密码登录,记住我功能不行。 
         */
        @Override
        @SneakyThrows
        protected void configure(HttpSecurity http) {
            http.httpBasic().and()
                    //禁用跨站请求伪造(CSRF)保护。
                    .csrf().disable()
                    .authorizeRequests().anyRequest().fullyAuthenticated();
        } 
    
    }
    

    当使用,增加了SecurityAdapter后,我们重新请求http://127.0.0.1:8080/user/123/roles/222,得到界面如下:

    image

    可以看到,登录界面的样式被美化了。

    设置访问二(推荐)

    我们还可以使用第二种方法,来做用户密码的配置。

    通过重写configure(AuthenticationManagerBuilder auth)函数,来创建用户,这种方式创建用户会将前面的bean-UserDetailsService给覆盖,即,用户只剩下这里创建的。

    代码如下:

    @Configuration
    @AllArgsConstructor
    public class SecurityAdapter extends WebSecurityConfigurerAdapter {
    
        /**
         * authenticated():用户需要通过用户名/密码登录,记住我功能也可以(remember-me)。
         * fullyAuthenticated()用户需要通过用户名/密码登录,记住我功能不行。
         */
        @Override
        @SneakyThrows
        protected void configure(HttpSecurity http) {
            http.httpBasic().and()
                    //禁用跨站请求伪造(CSRF)保护。
                    .csrf().disable()
                    .authorizeRequests().anyRequest().fullyAuthenticated();
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("kiba518")
                    .password(passwordEncoder().encode("123"))
                    .authorities(new ArrayList<>(0));
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
    }
    

    这里的用户是写死的,用户是可以修改成读取数据库的信息的。

    我们查看WebSecurityConfigurerAdapter的代码,可以看到他有注解@Order(100),数越大,执行越优先级越低,即,他的执行顺序是相对比较靠后的。

    授权OAuth2

    授权这个设计理念是这样,它是结合上面的security的操作,实现了一个普通的WebApp转换成授权服务器WebApp。

    授权服务器转换思路

    我们先了解一下security转授权服务器的思路。

    1,在这个应用里,创建一个auth接口,然后任何人想访问这个接口,就都需要输入账户密码了。

    2,我们这个auth接口的返回值是个code,然后我们的前端,或者其他调用接口的APP,就可以把这个code作为用户登录的token了,。

    3,然后我们再做一个接口,接受一个token参数,可以验证token是否有效。

    这样我们这个授权服务器的搭建思路就构建完成了。

    但按这个思路,我们需要做很多操作,比如创建接口,缓存token等等,现在spring提供了一个Oauth2的包,他可以帮我们实现这些接口定义。

    OAuth2的接口如下,可以自行研究。

    /oauth/authorize:授权端点

    /oauth/token:获取令牌端点

    /oauth/confirm_access:用户确认授权提交端点

    /oauth/error:授权服务错误信息端点

    /oauth/check_token:用于资源服务访问的令牌解析端点

    /oauth/token_key:提供公有密匙的端点,如果使用JWT令牌的话

    实现授权服务器

    现在我们实现一个授权服务器。

    先添加OAuth2的引用。

     <dependency>
                <groupId>org.springframework.security.oauthgroupId>
                <artifactId>spring-security-oauth2artifactId>
                <version>2.4.0.RELEASEversion>
            dependency>
    

    然后增加配置文件AuthorizationConfig。

    @Configuration
    @EnableAuthorizationServer //开启授权服务
    public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        private AuthenticationManager authenticationManager;
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            //允许表单提交
            security.allowFormAuthenticationForClients()
                    .checkTokenAccess("isAuthenticated()");
        }
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    .withClient("client-kiba") //客户端唯一标识(client_id)
                    .secret(passwordEncoder.encode("kiba518-123456")) //客户端的密码(client_secret),这里的密码应该是加密后的
                    .authorizedGrantTypes("password") //授权模式标识,共4种模式[授权码(authorization-code)隐藏式(implicit) 密码式(password)客户端凭证(client credentials)]
                    .scopes("read_scope"); //作用域
    
        }
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager);
        }
    
    }
    
    

    然后打开SecurityAdapter,增加一个bean,如下,目的是让上面的AuthorizationConfig里Autowired的authenticationManager可以实例化。

    @Bean
        public AuthenticationManager authenticationManager() throws Exception {
            return super.authenticationManager();
        }
    

    然后使用APIFox调用一下/oauth/token接口。

    先选择auth,输入账号密码,这个账号密码就是AuthorizationConfig里配置的客户端id和密码。

    image

    这个数据在请求时,会进行base64编码,然后以http的header属性Authorization的值的模式传递,如下。

    image

    然后输入参数,参数里scope和grant_type要和AuthorizationConfig里定义的scopes和authorizedGrantTypes一样,如下。

    image

    请求后,得到结果,如上图。

    我们得到"access_token": "19d37af2-6e13-49c3-bf19-30a738b56886"。

    有了access_token后,我们的前端其实就已经可以进行各种骚操作了。

    资源服务

    这个是Oauth为我们提供的一项很好用的功能。

    我们创建一个项目做为资源服务。

    添加依赖,版本与上面相同。

      <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-securityartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.security.oauthgroupId>
                <artifactId>spring-security-oauth2artifactId>
                <version>2.4.0.RELEASEversion>
            dependency>
    

    然后编写资源配置,代码如下:

    @Configuration
    @EnableResourceServer
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
        @Bean
        public RemoteTokenServices remoteTokenServices() {
            final RemoteTokenServices tokenServices = new RemoteTokenServices();
            tokenServices.setClientId("client-kiba");
            tokenServices.setClientSecret("kiba518-123456");
            tokenServices.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");//这个接口是oauth自带的
            return tokenServices;
        }
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.stateless(true);
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            //session创建策略
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
            //所有请求需要认证
            http.authorizeRequests().anyRequest().authenticated();
        }
    }
    

    因为添加了spring-boot-starter-security,所以,我们请求这个资源WebApp,就都需要输入账号密码。

    但因为,我们配置了ResourceServerConfig,这里我们配置了远程token服务,设置的信息是我们上面创建授权服务的信息。所以,在访问这个WebApp时,我们提供token即可。

    使用APIFOX测试,先添加auth的token,内容是来自于上面,/oauth/token的返回值access_token的值。

    image

    然后请求user接口,我这user接口没有参数,请求结果如下:

    image

    总结

    这个授权服务挺好用的,就是配置太繁琐了,初学者不太好理解,而且功能太多,配置太闹心。

    这个资源服务还是很贴心的,他提我们实现了,tokencheck的部分,但要注意的是,他这tokencheck是基于http请求的。

    虽然Oath很好用,但,我还是觉得,这个认证部分自己写比较好,我们可以根据项目的需求,设计轻量级的授权认证。

    比如,我们想减少http请求,把部分tokencheck在缓存内进行check,那使用oauth时,修改起来就会很头疼。如果是自己写的授权服务器,就不会有修改困难的问题。


    注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!



    若您觉得这篇文章还不错,请点击下方的【推荐】,非常感谢!

    https://www.cnblogs.com/kiba/p/18252859

  • 相关阅读:
    AI 大战高考作文!实测 ChatGPT、文心一言、通义千问等 8 款“神器”
    MySQL数据库 #3
    maven在centos7中配置教程
    记:一次关于paddlenlp、python、版本之间的兼容性问题
    智慧医院的建设包括哪些方面?医院数字孪生信息化建设标准方案
    UE5——C++编译MSB3073报错
    考试宝导出文件
    形态学算法应用之重建开操作的python实现——数字图像处理
    Win10系统电脑没有键盘怎么启用软键盘
    11.springboot监控
  • 原文地址:https://www.cnblogs.com/kiba/p/18252859