Spring Security OAuth2 JWT 认证服务器配置

四种授权模式

  • 授权码模式
  • 密码模式
  • 客户端模式
  • 简化模式

密码模式

  • grant_type:授权类型,必选,此处固定值password
  • username:表示用户名,必选
  • password:表示用户密码,必选
  • scope:权限范围,可选

(1)获取access_token

http://localhost:9001/oauth/token?username=user&password=secret&grant_type=password&client_id=client_password&client_secret=secret
{
    "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOlt7ImF1dGhvcml0eSI6IlJPTEUifV0sImF1ZCI6WyJyZXNvdXJjZV9wYXNzd29yZF9pZCJdLCJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsidGVzdCIsImNlc2hpIl0sImV4cCI6MTU2NTM0NzQ3MiwiYXV0aG9yaXRpZXMiOlsiUk9MRSJdLCJqdGkiOiJlNTc5ZjVlYy1kNjQ1LTQ1OWYtOWM1Mi05NjllYjI5YzFjMjUiLCJjbGllbnRfaWQiOiJjbGllbnRfcGFzc3dvcmQiLCJ1c2VybmFtZSI6InVzZXIifQ.igWUEbbR-wNvDCW66OPJ4xFfIjVc8UFA28NnJXLr4KA",
    "token_type":"bearer",
    "refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOlt7ImF1dGhvcml0eSI6IlJPTEUifV0sImF1ZCI6WyJyZXNvdXJjZV9wYXNzd29yZF9pZCJdLCJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsidGVzdCIsImNlc2hpIl0sImF0aSI6ImU1NzlmNWVjLWQ2NDUtNDU5Zi05YzUyLTk2OWViMjljMWMyNSIsImV4cCI6MTU2NTM0Nzc3MiwiYXV0aG9yaXRpZXMiOlsiUk9MRSJdLCJqdGkiOiI0NjUzNDVkNy00ZTIxLTQxMzQtYTNlNC0xNjY4Yzk5NWJkYjMiLCJjbGllbnRfaWQiOiJjbGllbnRfcGFzc3dvcmQiLCJ1c2VybmFtZSI6InVzZXIifQ.oxLvqsVgD2IfpcxW52K7eJjqU4JJ4eadrHvJtzMo85A",
    "expires_in":899,
    "scope":"test ceshi",
    "principal":[
        {
            "authority":"ROLE"
        }
    ],
    "username":"user",
    "jti":"e579f5ec-d645-459f-9c52-969eb29c1c25"
}

(2)通过refresh_token获取access_token

http://localhost:9001/oauth/token?grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOlt7ImF1dGhvcml0eSI6IlJPTEUifV0sImF1ZCI6WyJyZXNvdXJjZV9wYXNzd29yZF9pZCJdLCJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsidGVzdCIsImNlc2hpIl0sImF0aSI6ImU1NzlmNWVjLWQ2NDUtNDU5Zi05YzUyLTk2OWViMjljMWMyNSIsImV4cCI6MTU2NTM0Nzc3MiwiYXV0aG9yaXRpZXMiOlsiUk9MRSJdLCJqdGkiOiI0NjUzNDVkNy00ZTIxLTQxMzQtYTNlNC0xNjY4Yzk5NWJkYjMiLCJjbGllbnRfaWQiOiJjbGllbnRfcGFzc3dvcmQiLCJ1c2VybmFtZSI6InVzZXIifQ.oxLvqsVgD2IfpcxW52K7eJjqU4JJ4eadrHvJtzMo85A&client_id=client_password&client_secret=secret
{
    "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOiJ1c2VyIiwiYXVkIjpbInJlc291cmNlX3Bhc3N3b3JkX2lkIl0sInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyJ0ZXN0IiwiY2VzaGkiXSwiZXhwIjoxNTY1MzQ3NzM3LCJhdXRob3JpdGllcyI6WyJST0xFIl0sImp0aSI6IjJjZGY4YTgyLTE1NzEtNDI0YS05MjFkLTU2MDQzZTkxN2EwNyIsImNsaWVudF9pZCI6ImNsaWVudF9wYXNzd29yZCIsInVzZXJuYW1lIjoidXNlciJ9.lHVuK9L1ViegxnLmd0RijgJjfKliOCoCVa7taN35S8k",
    "token_type":"bearer",
    "refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOiJ1c2VyIiwiYXVkIjpbInJlc291cmNlX3Bhc3N3b3JkX2lkIl0sInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyJ0ZXN0IiwiY2VzaGkiXSwiYXRpIjoiMmNkZjhhODItMTU3MS00MjRhLTkyMWQtNTYwNDNlOTE3YTA3IiwiZXhwIjoxNTY1MzQ3NzcyLCJhdXRob3JpdGllcyI6WyJST0xFIl0sImp0aSI6IjQ2NTM0NWQ3LTRlMjEtNDEzNC1hM2U0LTE2NjhjOTk1YmRiMyIsImNsaWVudF9pZCI6ImNsaWVudF9wYXNzd29yZCIsInVzZXJuYW1lIjoidXNlciJ9.wziLAaYVxGswJNS_Wal_SE6lNsDd14f4a7UrTFoY2rM",
    "expires_in":899,
    "scope":"test ceshi",
    "principal":"user",
    "username":"user",
    "jti":"2cdf8a82-1571-424a-921d-56043e917a07"
}

授权码模式

  • client_id:客户端ID,必选
  • response_type:必须为code,必选
  • redirect_uri:回掉url,必选

(1)获取授权码

http://localhost:9001/oauth/authorize?client_id=auth_code&response_type=code&redirect_uri=http://localhost:9001/auth_user/get_auth_code

(2)获取access_token

http://localhost:9001/oauth/token?grant_type=authorization_code&code=QjDs33&client_id=auth_code&client_secret=secret&redirect_uri=http://localhost:9001/auth_user/get_auth_code
{
    "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOlt7ImF1dGhvcml0eSI6IlJPTEUifV0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2VsZWN0IiwicmVwbGFjZSIsImluc2VydCIsInVwZGF0ZSIsImRlbCJdLCJleHAiOjE1NjUzNDI3NDYsImF1dGhvcml0aWVzIjpbIlJPTEUiXSwianRpIjoiNDUzOTY1ZWYtNDdiOC00MTc4LWFmYWUtNzlmMzljODhjZWEzIiwiY2xpZW50X2lkIjoiYXV0aF9jb2RlIiwidXNlcm5hbWUiOiJhZG1pbiJ9.qvjdiaTXjQVYikgUnMGvssZKwILiuzi9mAS65R1lzfY",
    "token_type":"bearer",
    "refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOlt7ImF1dGhvcml0eSI6IlJPTEUifV0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2VsZWN0IiwicmVwbGFjZSIsImluc2VydCIsInVwZGF0ZSIsImRlbCJdLCJhdGkiOiI0NTM5NjVlZi00N2I4LTQxNzgtYWZhZS03OWYzOWM4OGNlYTMiLCJleHAiOjE1NjUzNDMwNDYsImF1dGhvcml0aWVzIjpbIlJPTEUiXSwianRpIjoiYjk3OWYwNmUtMDgyNy00MjhlLTkxNmUtZDdkNTljOThlNzJlIiwiY2xpZW50X2lkIjoiYXV0aF9jb2RlIiwidXNlcm5hbWUiOiJhZG1pbiJ9.HYhycwL9lT56yMuJu5y0hz7BfPhd1Gw3eweQWsWodQA",
    "expires_in":899,
    "scope":"select replace insert update del",
    "principal":[
        {
            "authority":"ROLE"
        }
    ],
    "username":"admin",
    "jti":"453965ef-47b8-4178-afae-79f39c88cea3"
}

(3)通过refresh_token获取access_token

http://localhost:9001/oauth/token?grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOlt7ImF1dGhvcml0eSI6IlJPTEUifV0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2VsZWN0IiwicmVwbGFjZSIsImluc2VydCIsInVwZGF0ZSIsImRlbCJdLCJhdGkiOiI0NTM5NjVlZi00N2I4LTQxNzgtYWZhZS03OWYzOWM4OGNlYTMiLCJleHAiOjE1NjUzNDMwNDYsImF1dGhvcml0aWVzIjpbIlJPTEUiXSwianRpIjoiYjk3OWYwNmUtMDgyNy00MjhlLTkxNmUtZDdkNTljOThlNzJlIiwiY2xpZW50X2lkIjoiYXV0aF9jb2RlIiwidXNlcm5hbWUiOiJhZG1pbiJ9.HYhycwL9lT56yMuJu5y0hz7BfPhd1Gw3eweQWsWodQA&client_id=auth_code&client_secret=secret
{
    "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOiJhZG1pbiIsInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2VsZWN0IiwicmVwbGFjZSIsImluc2VydCIsInVwZGF0ZSIsImRlbCJdLCJleHAiOjE1NjUzNDI4MjMsImF1dGhvcml0aWVzIjpbIlJPTEUiXSwianRpIjoiNTc1MDIyM2YtZGVkYi00OTQyLWE3OWYtNGM0NzkwNjRiZDlhIiwiY2xpZW50X2lkIjoiYXV0aF9jb2RlIiwidXNlcm5hbWUiOiJhZG1pbiJ9.uUEnoekC-ZESh1ongSsbwOx0ZtsVxV4L2WWnNb42NXQ",
    "token_type":"bearer",
    "refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOiJhZG1pbiIsInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2VsZWN0IiwicmVwbGFjZSIsImluc2VydCIsInVwZGF0ZSIsImRlbCJdLCJhdGkiOiI1NzUwMjIzZi1kZWRiLTQ5NDItYTc5Zi00YzQ3OTA2NGJkOWEiLCJleHAiOjE1NjUzNDMwNDYsImF1dGhvcml0aWVzIjpbIlJPTEUiXSwianRpIjoiYjk3OWYwNmUtMDgyNy00MjhlLTkxNmUtZDdkNTljOThlNzJlIiwiY2xpZW50X2lkIjoiYXV0aF9jb2RlIiwidXNlcm5hbWUiOiJhZG1pbiJ9.wefqVCYmbQ7hgTRHFAYCxtElvB_iuE69sOSXbxiMbH8",
    "expires_in":899,
    "scope":"select replace insert update del",
    "principal":"admin",
    "username":"admin",
    "jti":"5750223f-dedb-4942-a79f-4c479064bd9a"
}

Client模式

  • client_id: 客户端ID,必选
  • client_secret: 客户端密码,必选
  • grant_type: 必须为password,必选
  • scope: 授权范围,必选
http://localhost:9001/oauth/token?grant_type=client_credentials&scope=insert&client_id=client&client_secret=secret
{
    "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJpbnNlcnQiXSwiZXhwIjoxNTY1MzQ4MTEwLCJqdGkiOiIzOGI3ODMzYS1kMGIxLTQzMjYtODIxYy00YTc2NmM4ZGVhZGEiLCJjbGllbnRfaWQiOiJjbGllbnQifQ.W0ANHlumi1nYHp7wesg9_oU6tYCMDXDE68hlM2jT8ng",
    "token_type":"bearer",
    "expires_in":899,
    "scope":"insert",
    "jti":"38b7833a-d0b1-4326-821c-4a766c8deada"
}

极简模式

http://localhost:9001/oauth/authorize?client_id=client_implicit&response_type=token&redirect_uri=http://localhost:9001/auth_user/get_token_info
{
    "authorities":[
        {
            "authority":"ROLE"
        }
    ],
    "details":{
        "remoteAddress":"0:0:0:0:0:0:0:1",
        "sessionId":"15672F0BFB67D987A68E18C4219FFA1F"
    },
    "authenticated":true,
    "principal":{
        "password":null,
        "username":"admin",
        "authorities":[
            {
                "authority":"ROLE"
            }
        ],
        "accountNonExpired":true,
        "accountNonLocked":true,
        "credentialsNonExpired":true,
        "enabled":true
    },
    "credentials":null,
    "name":"admin"
}

依赖pom

<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</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.0.10.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

认证服务器配置

/**
 * @Description: @EnableAuthorizationServer注解开启OAuth2授权服务机制
 * @Package: AuthorizationServerConfig
 * @Version: 1.0
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private OAuth2ClientDetailsService oAuth2ClientDetailsService;

    @Autowired
    private AuthUserDetailsService authUserDetailsService;

    /**
     * 用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里初始化,
     * 可以把客户端详情信息写死也可以写入内存或者数据库中
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //使用自定义ClientDetailsService初始化配置
        clients.withClientDetails(oAuth2ClientDetailsService);
    }

    /**
     * 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        //token持久化容器
        tokenServices.setTokenStore(tokenStore());
        //客户端信息
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        //自定义token生成
        tokenServices.setTokenEnhancer(accessTokenConverter());
        //access_token 的有效时长 (秒), 默认 12 小时
        tokenServices.setAccessTokenValiditySeconds(60 * 15);
        //refresh_token 的有效时长 (秒), 默认 30 天
        tokenServices.setRefreshTokenValiditySeconds(60 * 20);
        //是否支持refresh_token, 默认false
        tokenServices.setSupportRefreshToken(true);
        //是否复用refresh_token, 默认为true(如果为false, 则每次请求刷新都会删除旧的refresh_token, 创建新的refresh_token)
        tokenServices.setReuseRefreshToken(true);

        endpoints
                //通过authenticationManager开启密码授权
                .authenticationManager(authenticationManager)
                //自定义refresh_token刷新令牌对用户信息的检查,以确保用户信息仍然有效
                .userDetailsService(authUserDetailsService)
                //token相关服务
                .tokenServices(tokenServices)
                //控制TokenEndpoint端点请求访问的类型,默认HttpMethod.POST
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                /**
                 pathMapping用来配置端点URL链接,第一个参数是端点URL默认地址,第二个参数是你要替换的URL地址
                 上面的参数都是以“/”开头,框架的URL链接如下:
                 /oauth/authorize: 授权端点, 对应的类: AuthorizationEndpoint.java
                 /oauth/token: 令牌端点, 对应的类: TokenEndpoint.java
                 /oauth/confirm_access: 用户确认授权提交端点. 对应的类: WhitelabelApprovalEndpoint.java
                 /oauth/error: 授权服务错误信息端点
                 /oauth/check_token: 用于资源服务访问的令牌解析端点
                 /oauth/token_key: 提供公有密匙的端点, 如果你使用JWT令牌的话
                 */
                .pathMapping("/oauth/confirm_access", "/custom/confirm_access");
    }

    /**
     * 用来配置令牌端点(Token Endpoint)的安全约束
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.realm("OAuth2-Sample")
                .allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()");
    }

    /**
     * OAuth2 token持久化接口
     */
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    /**
     * 自定义token令牌增强器
     */
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new UserTokenEnhancer();
        accessTokenConverter.setSigningKey("123456");
        return accessTokenConverter;
    }
}

自定义ClientDetailsService实现类

/**
 * @Description: 用户认证
 * @Package: cn.appblog.security.oauth2.po.AuthUserDetailsService
 * @Version: 1.0
 */
@Component
public class AuthUserDetailsService implements UserDetailsService {
    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 根据用户名查询用户角色、权限等信息
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("用户信息验证: " + username);
        /**
         isEnabled 账户是否启用
         isAccountNonExpired 账户没有过期
         isCredentialsNonExpired 身份认证是否是有效的
         isAccountNonLocked 账户没有被锁定
         */
        return new User(username, passwordEncoder.encode("secret"),
                true,
                true,
                true,
                true,
                AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE"));
    }
}

自定义WebSecurityConfigurerAdapter实现类

/**
 * @Description: 启动基于Spring Security的安全认证
 * @Package: com.yaomy.security.oauth2.config.WebSecurityConfigurer
 * @Version: 1.0
 */
@Configuration
@EnableWebSecurity
public class BaseSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private AuthUserDetailsService authUserDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private UserAccessDeniedHandler accessDeniedHandler;
    @Autowired
    private UserAuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    private UserAuthenticationFailureHandler authenticationFailureHandler;
    @Autowired
    private UserAuthenticationSuccessHandler authenticationSuccessHandler;
    @Autowired
    private UserLogoutSuccessHandler logoutSuccessHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 指定支持基于表单的身份验证, 如果未指定FormLoginConfigurer#loginPage(String), 则将生成默认登录页面
                .formLogin()
                // 自定义登录页url, 默认为/login
                .loginPage("/test/login")
                // 登录请求拦截的url, 也就是form表单提交时指定的action
                .loginProcessingUrl("/user/login")
                // 用户名的请求字段 username
                .usernameParameter("username")
                // 密码的请求字段 默认为password
                .passwordParameter("password")
                // 登录成功
                // .successHandler(authenticationSuccessHandler)
                // 登录失败
                .failureHandler(authenticationFailureHandler)
                // 无条件允许访问
                .permitAll()
                .and()
                .requestMatchers()
                .anyRequest()
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/**")
                .permitAll()
                /* .and()
                  // 其它的请求要求必须有身份认证
                  .authorizeRequests()
                  .anyRequest()
                  .authenticated()
                  */
                .and()
                .logout()
                .logoutSuccessHandler(logoutSuccessHandler)
                .permitAll()
                .and()
                // 认证过的用户访问无权限资源时的处理
                .exceptionHandling().accessDeniedHandler(accessDeniedHandler);
        http.csrf().disable();
    }

    /**
     * Spring Security认证服务中的相关实现重新定义
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 加入自定义的安全认证
        auth.userDetailsService(this.authUserDetailsService).passwordEncoder(this.passwordEncoder());
        auth.authenticationProvider(this.authenticationProvider());
    }

    /**
     * 需要主动暴漏AuthenticationManager,否则会报异常
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * Spring security认证Bean
     */
    @Bean
    public AuthenticationProvider authenticationProvider() {
        AuthenticationProvider authenticationProvider = new UserAuthenticationProvider();
        return authenticationProvider;
    }

    /**
     * 自定义加密器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

自定义JWT token增强类

/**
 * @Description: 用户自定义token令牌,包括access_token和refresh_token
 * @Package: cn.appblog.security.oauth2.enhancer.UserTokenEnhancer
 * @Version: 1.0
 */
public class UserTokenEnhancer extends JwtAccessTokenConverter {
    /**
     * 重新定义令牌token
     */
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        //授权类型 authorization_code、password、client_credentials、refresh_token、implicit
        String grantType = authentication.getOAuth2Request().getGrantType();
        if (!StringUtils.equals(grantType, "client_credentials")) {
            String userName = authentication.getUserAuthentication().getName();
            // 与登录时候放进去的UserDetail实现类一直查看link{SecurityConfiguration}
            Object principal = authentication.getUserAuthentication().getPrincipal();
            /**
             自定义一些token属性
             **/
            Map<String, Object> additionalInformation = Maps.newHashMap();
            additionalInformation.put("username", userName);
            if (principal instanceof User) {
                additionalInformation.put("principal", ((User) principal).getAuthorities());
            } else {
                additionalInformation.put("principal", principal);
            }
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);

        }
        OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication);
        return enhancedToken;
    }
}

用户自定义身份认证AuthenticationProvider

/**
 * @Description: 用户自定义身份认证
 * @Package: cn.appblog.security.oauth2.provider.MyAuthenticationProvider
 * @Version: 1.0
 */
@Slf4j
@Component
public class UserAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    private AuthUserDetailsService authUserDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 认证处理,返回一个Authentication的实现类则代表认证成功,返回null则代表认证失败
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();
        //获取用户信息
        UserDetails user = authUserDetailsService.loadUserByUsername(username);
        log.info("password: {}, user.getPassword(): {}", password, user.getPassword());
        log.info("passwordEncoder.matches: {}", passwordEncoder.matches(password, user.getPassword()));
        //比较前端传入的密码明文和数据库中加密的密码是否相等
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new DisabledException("用户密码不正确");
        }
        //获取用户权限信息
        Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
        return new UsernamePasswordAuthenticationToken(user, password, authorities);
    }

    /**
     * 如果该AuthenticationProvider支持传入的Authentication对象,则返回true
     */
    @Override
    public boolean supports(Class<?> aClass) {
        return aClass.equals(UsernamePasswordAuthenticationToken.class);
    }
}

自定义登陆页面

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>

<style>
    .login-container {
        margin: 50px;
        width: 100%;
    }
    .form-container {
        margin: 0px auto;
        width: 50%;
        text-align: center;
        box-shadow: 1px 1px 10px #888888;
        height: 300px;
        padding: 5px;
    }
    input {
        margin-top: 10px;
        width: 350px;
        height: 30px;
        border-radius: 3px;
        border: 1px #E9686B solid;
        padding-left: 2px;
    }
    .btn {
        width: 350px;
        height: 35px;
        line-height: 35px;
        cursor: pointer;
        margin-top: 20px;
        border-radius: 3px;
        background-color: #E9686B;
        color: white;
        border: none;
        font-size: 15px;
    }
    .title{
        margin-top: 5px;
        font-size: 18px;
        color: #E9686B;
    }
</style>
<body>
<div class="login-container">
    <div class="form-container">
        <p class="title">用户登录</p>
        <form name="loginForm" method="post" th:action="${loginProcessUrl}">
            <input type="text" name="username" placeholder="用户名"/>
            <br>
            <input type="password" name="password" placeholder="密码"/>
            <br>
            <button type="submit" class="btn">登    录</button>
        </form>
    </div>
</div>
</body>
</html>

自定义授权页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>授权</title>
</head>
<style>
    html{
        padding: 0px;
        margin: 0px;
    }
    .title {
        background-color: #E9686B;
        height: 50px;
        padding-left: 20%;
        padding-right: 20%;
        color: white;
        line-height: 50px;
        font-size: 18px;
    }
    .title-left{
        float: right;
    }
    .title-right{
        float: left;
    }
    .title-left a{
        color: white;
    }
    .container{
        clear: both;
        text-align: center;
    }
    .btn {
        width: 350px;
        height: 35px;
        line-height: 35px;
        cursor: pointer;
        margin-top: 20px;
        border-radius: 3px;
        background-color: #E9686B;
        color: white;
        border: none;
        font-size: 15px;
    }
</style>
<body style="margin: 0px">
<div class="title">
    <div class="title-right">Spring Security 授权</div>
    <div class="title-left">
        <a href="#help">帮助</a>
    </div>
</div>
<div class="container">
    <h3 th:text="${clientId}+' 请求授权,该应用将获取你的以下信息'"></h3>
    <form method="post" action="/oauth/authorize">
        <input type="hidden" name="user_oauth_approval" value="true">
        <!--<input type="hidden" name="_csrf" th:value="${_csrf.getToken()}"/>-->
        <ul style="list-style-type: none">
            <li th:each="s:${scope}">
                <div class="form-group"><a th:text="${s}"></a>: <input type="radio" th:name="'scope.'+${s}" value="true">Approve(授权) <input type="radio" th:name="'scope.'+${s}" value="false" checked="">Deny(拒绝)</div>
            </li>
        </ul>
        授权后表明你已同意 <a  href="#boot" style="color: #E9686B">服务协议</a><br>
        <button class="btn" type="submit"> 同意/授权</button>
    </form>
</div>
</body>
</html>

登录页及授权页渲染

/**
 * @Description: 自定义登陆页面
 * @Package: GrantController
 * @Version: 1.0
 */
@Controller
@SessionAttributes("authorizationRequest")
public class GrantController {
    @GetMapping("/test/login")
    public String index(Model model) {
        model.addAttribute("loginProcessUrl", "/user/login");
        return "login";
    }

    @RequestMapping("/custom/confirm_access")
    public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) throws Exception {
        AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest");
        ModelAndView view = new ModelAndView();
        view.setViewName("grant");
        view.addObject("clientId", authorizationRequest.getClientId());
        view.addObject("scope", authorizationRequest.getScope());
        System.out.println(authorizationRequest.getScope());
        System.out.println(authorizationRequest.getClientId());
        return view;
    }
}

自定义授权重定向相关接口

/**
 * @Description: 登录测试接口
 * @Package: LoginController
 * @Version: 1.0
 */
@RestController
@RequestMapping("auth_user")
public class LoginController {
    /**
     * 登录测试接口
     */
    @RequestMapping(value = "get_token_info", method = RequestMethod.GET)
    public Object login(HttpServletRequest request) {
        SecurityContext ctx = SecurityContextHolder.getContext();
        Authentication auth = ctx.getAuthentication();
        System.out.println(auth.getAuthorities() + " -- " + auth.getCredentials() + " -- " + auth.getDetails() + " -- " + auth.getPrincipal() + " -- " + auth.getName());
        return auth;
    }

    /**
     * 获取授权码
     */
    @RequestMapping("/get_auth_code")
    public String home(HttpServletRequest request) {
        String code = request.getParameter("code");
        System.out.println("code: " + code);
        if (StringUtils.isBlank(code)) {
            code = "denyAll";
        }
        return code;
    }
}

本文转载参考 原文 并加以调试

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/19/spring-security-oauth2-jwt-authentication-server-configuration/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
Spring Security OAuth2 JWT 认证服务器配置
四种授权模式 授权码模式 密码模式 客户端模式 简化模式 密码模式 grant_type:授权类型,必选,此处固定值password username:表示用户名,必选 password……
<<上一篇
下一篇>>
文章目录
关闭
目 录