0


SpringBoot集成Oauth2.0(密码模式)

Oauth2.0提供了四种认证方式:

授权码模式(authorization code)、
简化模式(implicit)、
密码模式(resource owner passwordcredentials)、
客户端模式(client credentials)

Oauth2.0中的四个重要角色
OAauth2.0包括的角色****说明资源拥有者通常为用户,也可以是应用程序,即该资源的拥有者。客户端本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源,比如:Android客户端、Web客户端(浏览器端)、微信客户端等。授权服务器(也称认证服务器)用于服务提供商对资源拥有者的身份进行认证、对访问资源进行授权,认证成功后会给客户端发放令牌 (access_token),作为客户端访问资源服务器的凭据。资源服务器存储资源的服务器。
废话不多说,直接上代码。

引入依赖

 <!--        springSecurity-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--        Oauth2-->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>
        <!--        jwt增强-->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>1.1.0.RELEASE</version>
        </dependency>

添加配置文件

SpringSecurity配置文件

import com.municipal.service.impl.UserAuthService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * @Author majinzhong
 * @Date 2023/6/12 19:11
 * @Version 1.0
 * URI拦截.
 */
@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserAuthService userAuthService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/oauth/**", "/oauth2/**","/login/**", "logout/**").permitAll()    // login 页面,所有用户都可以访问
                //.antMatchers("/home").hasAnyRole("USER", "com.municipal.pojo")   // home 页面,ADMIN 和 USER 都可以访问
                .anyRequest().authenticated()
//                .and()
//                .formLogin()
//                .loginPage("/login") // 自定义登录表单
                //.defaultSuccessUrl("/home", true)  // 登录成功跳转的页面,第二个参数true表示每次登录成功都是跳转到home,如果false则表示跳转到登录之前访问的页面
                //.failureUrl("/login?error=true")
//                .failureHandler(authenticationFailureHandler())  // 失败跳转的页面(比如用户名/密码错误),这里还是跳转到login页面,只是给出错误提示
                .and().logout().permitAll()  // 登出 所有用户都可以访问
                .and().csrf().disable();    // 关闭csrf,此时登出logout接收任何形式的请求;(默认开启,logout只接受post请求)
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        //设置登陆账户和密码
//        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
//        auth.inMemoryAuthentication().withUser("admin").
//                password(encoder.encode("123456")).authorities("ROLE ADMIN");
        //从数据库中读取账户密码
        auth.userDetailsService(userAuthService);
    }
    //---------------------
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

Oauth配置文件

import com.municipal.service.impl.UserAuthService;
import org.springframework.beans.factory.annotation.Autowired;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
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.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * 认证服务端配置.
 * @Author majinzhong
 * @Date 2023/7/5 17:24
 * @Version 1.0
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Resource
    AuthenticationManager authenticationManager;
//
//    @Resource
//    RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private CustomWebResponseExceptionTranslator customWebResponseExceptionTranslator;

    @Autowired
    UserAuthService userAuthService;

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Resource
    DataSource dataSource;

    @Override
    public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {
        // 内存存储配置信息+密码模式
        serviceConfig(0, clientDetailsServiceConfigurer, dataSource, "password");
    }

    /**
     * 服务配置,如授权方式,token过期时间等.
     *
     * @param flag                           内存和数据库标识,0:内存;1:数据库
     * @param clientDetailsServiceConfigurer 配置器
     * @param dataSource                     数据源
     * @param grantType                      授权类型,password:密码模式;authorization_code:授权码模式
     * @throws Exception 异常
     */
    private void serviceConfig(int flag, ClientDetailsServiceConfigurer clientDetailsServiceConfigurer, DataSource dataSource, String grantType) throws Exception {
//        if (flag == 1) {
//            clientDetailsServiceConfigurer.jdbc(dataSource);
//        } else {
            clientDetailsServiceConfigurer
                    //存储到内存中
                    .inMemory()
                    //配置client_id
                    .withClient("client")
                    //配置client-secret
                    .secret(new BCryptPasswordEncoder().encode("123456"))
                    //配置访问token的有效期
                    .accessTokenValiditySeconds(3600)
                    //配置刷新token的有效期
                    .refreshTokenValiditySeconds(864000)
                    //配置redirect_uri,用于授权成功后跳转
//                .redirectUris("http://www.baidu.com")
                    //配置申请的权限范围
                    .scopes("all")
                    //配置grant_type,表示授权类型
                    .authorizedGrantTypes("password","refresh_token");
//        }
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpointsConfig) throws Exception {
        // 默认情况下,授权码存储在内存中:InMemoryAuthorizationCodeServices,
        // 所以,不用配置
        endpointsConfig.tokenStore(new InMemoryTokenStore()) //增加 TokenStore 配置
                .authenticationManager(authenticationManager) //使用密码模式需要配置
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST) //支持GET,POST请求
                .userDetailsService(userAuthService); //设置userDetailsService刷新token时候会用到
        endpointsConfig.exceptionTranslator(customWebResponseExceptionTranslator);//错误异常
    }

//    /**
//     * 配置授权码存储位置.
//     *
//     * @param flag                                 授权码存储位置标识:0,内存;1:数据库
//     * @param endpointsConfig                      端配置
//     * @param randomValueAuthorizationCodeServices 授权码存储位置
//     */
//    private void codeStoreEndpointConfig(int flag, AuthorizationServerEndpointsConfigurer endpointsConfig, RandomValueAuthorizationCodeServices randomValueAuthorizationCodeServices) {
//        if (flag == 1) {
//            // 授权码存储数据库,需要配置jdbc存储
//            endpointsConfig.authorizationCodeServices(randomValueAuthorizationCodeServices);
//        }
//    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer serverSecurityConfig) throws Exception {
        //允许表单认证
        serverSecurityConfig.allowFormAuthenticationForClients();
        //添加tokan校验失败返回消息
        serverSecurityConfig.authenticationEntryPoint(new AuthExceptionEntryPoint());
    }
}

认证资源配置

import com.municipal.service.impl.UserAuthService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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;

/**
 * 认证资源配置.
 * @Author majinzhong
 * @Date 2023/7/5 17:29
 * @Version 1.0
 */
@Configuration
@EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {

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

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.requestMatchers()
                .antMatchers("/**")
                .and()
                .authorizeRequests()
                .antMatchers("/**")
                .authenticated();
    }
}

配置UserDetailsService

import com.municipal.pojo.UserInfo;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * @Author majinzhong
 * @Date 2023/6/29 10:51
 * @Version 1.0
 */
@Component
public class UserAuthService implements UserDetailsService {
//    @Autowired
//    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//        User user = userMapper.getByUsername(username);
//        if(user == null){
//            throw new UsernameNotFoundException("not found the user:"+username);
//        }else{
//            return user;
//        }
        if("admin".equals(username)){
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            return new UserInfo(1, "admin", encoder.encode("123456"));
        }else{
            throw new UsernameNotFoundException("not found the user:"+username);
        }
    }
}

用户实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @Author majinzhong
 * @Date 2023/6/29 10:49
 * @Version 1.0
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo implements UserDetails {

    private Integer userId;
    private String userName;
    private String password;

    //将我们的 权限字符串进行分割,并存到集合中,最后供 AuthenticationProvider 使用
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        return null;
    }

    //将我们的 密码 转换成 AuthenticationProvider 能够认识的 password
    @Override
    public String getPassword() {
        return this.password;
    }

    //将我们的 账户 转换成 AuthenticationProvider 能够认识的 username
    @Override
    public String getUsername() {
        return this.userName;
    }

    //都返回 true
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

至此,OAuth已经配置成功了,但当浏览器访问/oauth/token接口返回401的时候,页面会出现弹框

是因为response headers里面有Www-Authenticate: Basic realm="oauth2/client"

为了把这个弹框去掉也是费了一番大功夫

密码模式错误处理类(WebResponseExceptionTranslator)

import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.stereotype.Component;

/**
 *
 * @ClassName:  CustomWebResponseExceptionTranslator
 * @Description:password模式错误处理,自定义登录失败异常信息
 * @Author majinzhong
 * @Date 2023/7/3 16:33
 * @Version 1.0
 */
@Component
public class CustomWebResponseExceptionTranslator implements WebResponseExceptionTranslator {
    @Override
    public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {

        OAuth2Exception oAuth2Exception = (OAuth2Exception) e;
        return ResponseEntity
                //.status(oAuth2Exception.getHttpErrorCode())
                .status(200)
                .body(new CustomOauthException(oAuth2Exception.getMessage()));
    }
}

认证失败序列化类(CustomOauthExceptionSerializer)

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Date;
import java.util.Map;

/**
 *
 * @ClassName:  CustomOauthExceptionSerializer
 * @Description:password模式错误处理,自定义登录失败异常信息
 * @Author majinzhong
 * @Date 2023/7/3 16:32
 * @Version 1.0
 */
public class CustomOauthExceptionSerializer extends StdSerializer<CustomOauthException> {

    private static final long serialVersionUID = 1478842053473472921L;

    public CustomOauthExceptionSerializer() {
        super(CustomOauthException.class);
    }
    @Override
    public void serialize(CustomOauthException value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        gen.writeStartObject();
        gen.writeStringField("error", String.valueOf(value.getHttpErrorCode()));
//        gen.writeStringField("message", value.getMessage());
        gen.writeStringField("message", "用户名或密码错误");
        gen.writeStringField("path", request.getServletPath());
        gen.writeStringField("timestamp", String.valueOf(new Date().getTime()));
        if (value.getAdditionalInformation()!=null) {
            for (Map.Entry<String, String> entry :
                    value.getAdditionalInformation().entrySet()) {
                String key = entry.getKey();
                String add = entry.getValue();
                gen.writeStringField(key, add);
            }
        }
        gen.writeEndObject();
    }
}

认证异常返回类(CustomOauthException)

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;

/**
 *
 * @ClassName:  CustomOauthException
 * @Description:password模式错误处理,自定义登录失败异常信息
 * @Author majinzhong
 * @Date 2023/7/3 16:32
 * @Version 1.0
 */
@JsonSerialize(using = CustomOauthExceptionSerializer.class)
public class CustomOauthException extends OAuth2Exception {
    public CustomOauthException(String msg) {
        super(msg);
    }
}

tokan校验失败返回信息(AuthExceptionEntryPoint)同时解决浏览器打开出现弹框的问题

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 自定义AuthExceptionEntryPoint用于tokan校验失败返回信息
 * @Author majinzhong
 * @Date 2023/7/3 16:31
 * @Version 1.0
 */
public class AuthExceptionEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws ServletException {

        Map<String, Object> map = new HashMap<>();
        //401 未授权
        map.put("error", HttpServletResponse.SC_UNAUTHORIZED);
        map.put("message", authException.getMessage());
        map.put("path", request.getServletPath());
        map.put("timestamp", String.valueOf(new Date().getTime()));
        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        try {
            ObjectMapper mapper = new ObjectMapper();
            mapper.writeValue(response.getOutputStream(), map);
        } catch (Exception e) {
            throw new ServletException();
        }
    }

}

此时,springboot集成oauth就完成了,进行测试(post和get方式都行)

获取token接口

127.0.0.1:8088/oauth/token?username=admin&password=123456&grant_type=password&client_id=client&client_secret=123456&scope=all

刷新token接口

127.0.0.1:8088/oauth/token?grant_type=refresh_token&client_id=client&client_secret=123456&refresh_token=2be033f2-6dab-46fb-bfc8-3f97b6a46dd4

get请求示例:

方式一

方式二

post请求和get请求的方式二一致

标签: spring boot 后端 java

本文转载自: https://blog.csdn.net/m0_71817461/article/details/131521086
版权归原作者 奔驰的小野码 所有, 如有侵权,请联系我们删除。

“SpringBoot集成Oauth2.0(密码模式)”的评论:

还没有评论