springsecurity授权码模式项目实战

一、前言

1、客户端详情表oauth_client_details
client_id:客户端ID
resources_ids:可访问的资源服务器ID,不写则不校验
client_secret:客户端密码,此处不能是明文,需要加密
scope:客户端授权范围,不指定默认不校验范围
authorized_grant_types:客户端授权类型,支持多个使用逗号分隔。(authorization_code,password,implicit,client_credentials,refresh_token)
web_server_redirect_uri:服务器回调地址
autoapprove:false:显示授权点击页,true:不显示自动授权
2、默认提供的令牌访问端点
/oauth/authorize:申请授权码code
/oauth/token:获取令牌token
/oauth/check_token:用于资源服务器请求端点来检查令牌是否有效
/oauth/confirm_access:用户确认授权提交
/oauth/error:授权服务错误信息
/oauth/token_key:提供公有密钥的端点,使用JWT令牌时会使用
3、授权码模式流程
1、获取授权码code:
    浏览器输入:http://localhost:7001/auth/oauth/authorize?client_id=yyy&response_type=code
    用户登录
    用户授权:autoapprove为true则跳过授权
    携带code跳转到web_server_redirect_uri:https://www.xxx.com/?code=gEAogD
2、获取token
    POST http://localhost:7001/auth/oauth/token
    Authorization: Basic 
        Username=client_id
        Password=client_secret
    Content-Type: application/x-www-form-urlencoded
        grant_type=authorization_code
        code=gEAogD

二、代码

1、安全配置类
package com.wuxi.project.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;

/**
 * 安全配置类
 */
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsServiceImpl;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsServiceImpl);
    }

    @Bean // 密码模式需要使用
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}
2、认证服务器配置
package com.wuxi.project.auth.config;

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.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.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenStore;

import javax.sql.DataSource;

@Configuration
@EnableAuthorizationServer // 标识为认证服务器
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Bean
    public ClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    /**
     * 配置被允许访问此认证服务器的客户端信息
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // jdbc管理客户端
        clients.withClientDetails(jdbcClientDetailsService());
    }

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsServiceImpl;

    @Autowired
    private TokenStore tokenStore;

    @Bean
    public AuthorizationCodeServices jdbcAuthorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    /**
     * 关于认证服务器端点配置
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 密码模式需要使用
        endpoints.authenticationManager(authenticationManager);
        // 刷新令牌需要使用
        endpoints.userDetailsService(userDetailsServiceImpl);
        // 令牌的管理方式
        endpoints.tokenStore(tokenStore);
        // 授权码管理策略
        endpoints.authorizationCodeServices(jdbcAuthorizationCodeServices());
    }

    /**
     * 令牌端点的安全配置
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 认证后可访问 /oauth/token_key , 默认拒绝访问
        security.tokenKeyAccess("permitAll()");
        // 认证后可访问 /oauth/check_token , 默认拒绝访问
        security.checkTokenAccess("isAuthenticated()");
    }

}
3、Token配置
package com.wuxi.project.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;

@Configuration
public class TokenConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public TokenStore tokenStore() {
        // 在使用Jwt管理令牌时,出现了用户无法授权范围的问题,还未能解决
        return new JdbcTokenStore(dataSource);
    }

}
4、PasswordEncoder配置
package com.wuxi.project.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class PasswordEncoderConfig {

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

}
5、UserDetailsService
package com.wuxi.project.auth.service;

import com.wuxi.project.auth.entities.JwtUser;
import com.wuxi.project.common.entities.SysMenu;
import com.wuxi.project.common.entities.SysUser;
import com.wuxi.project.common.feign.IFeignSystemController;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private IFeignSystemController feignSystemController;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1. 判断用户名是否为空
        if (StringUtils.isEmpty(username)) {
            throw new BadCredentialsException("用户名不能为空");
        }
        // 2. 通过用户名查询数据库中的用户信息
        SysUser sysUser = feignSystemController.findUserByUsername(username);
        if (sysUser == null) {
            throw new BadCredentialsException("用户名或密码错误");
        }

        // 3. 通过用户id去查询数据库的拥有的权限信息
        List<SysMenu> menuList = feignSystemController.findMenuListByUserId(sysUser.getId());

        // 4. 封装权限信息(权限标识符code)
        List<GrantedAuthority> authorities = null;
        if (CollectionUtils.isNotEmpty(menuList)) {
            authorities = new ArrayList<>();
            for (SysMenu menu : menuList) {
                // 权限标识
                String code = menu.getCode();
                authorities.add(new SimpleGrantedAuthority(code));
            }
        }

        // 5. 构建UserDetails接口的实现类JwtUser对象
        JwtUser jwtUser = new JwtUser(sysUser.getId(), sysUser.getUsername(), sysUser.getPassword(),
                sysUser.getNickName(), sysUser.getImageUrl(), sysUser.getMobile(), sysUser.getEmail(),
                sysUser.getIsAccountNonExpired(), sysUser.getIsAccountNonLocked(),
                sysUser.getIsCredentialsNonExpired(), sysUser.getIsEnabled(),
                authorities);

        return jwtUser;
    }
}
6、UserDetails
package com.wuxi.project.auth.entities;

import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.List;

@Data
public class JwtUser implements UserDetails {

    /**
     * 用户ID
     */
    private String uid;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码,加密存储, admin/1234
     */
    @JSONField(serialize = false) // 忽略转json
    private String password;

    /**
     * 昵称
     */
    private String nickName;

    /**
     * 头像url
     */
    private String imageUrl;

    /**
     * 注册手机号
     */
    private String mobile;

    /**
     * 注册邮箱
     */
    private String email;

    /**
     * 帐户是否过期(1 未过期,0已过期)
     * 1 true 0 false
     */
    @JSONField(serialize = false) // 忽略转json
    private boolean isAccountNonExpired; // 不要写小写 boolean

    /**
     * 帐户是否被锁定(1 未过期,0已过期)
     */
    @JSONField(serialize = false) // 忽略转json
    private boolean isAccountNonLocked;

    /**
     * 密码是否过期(1 未过期,0已过期)
     */
    @JSONField(serialize = false) // 忽略转json
    private boolean isCredentialsNonExpired;

    /**
     * 帐户是否可用(1 可用,0 删除用户)
     */
    @JSONField(serialize = false) // 忽略转json
    private boolean isEnabled;

    /**
     * 封装用户拥有的菜单权限标识
     */
    @JSONField(serialize = false) // 忽略转json
    private List<GrantedAuthority> authorities;

    //    isAccountNonExpired 是 Integer 类型接收,然后转 boolean
    public JwtUser(String uid, String username, String password,
                   String nickName, String imageUrl, String mobile, String email,
                   Integer isAccountNonExpired, Integer isAccountNonLocked,
                   Integer isCredentialsNonExpired, Integer isEnabled,
                   List<GrantedAuthority> authorities) {
        this.uid = uid;
        this.username = username;
        this.password = password;
        this.nickName = nickName;
        this.imageUrl = imageUrl;
        this.mobile = mobile;
        this.email = email;
        this.isAccountNonExpired = isAccountNonExpired == 1 ? true : false;
        this.isAccountNonLocked = isAccountNonLocked == 1 ? true : false;
        this.isCredentialsNonExpired = isCredentialsNonExpired == 1 ? true : false;
        this.isEnabled = isEnabled == 1 ? true : false;
        this.authorities = authorities;
    }

}

三、资源服务器

1、Token配置
package com.wuxi.project.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;


/**
 * @Auther: ll
 */
@Configuration
public class TokenConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

}
2、资源服务器配置
package com.wuxi.project.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
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;
import org.springframework.security.oauth2.provider.token.TokenStore;

/**
 * 资源服务器相关配置类
 *
 * @Auther: ll
 */
@Configuration
@EnableResourceServer // 标识为资源服务器,请求服务中的资源,就要带着token过来,找不到token或token是无效访问不了资源
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级别权限控制
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    public static final String RESOURCE_ID = "system";

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        // 当前资源服务器的资源id,认证服务会认证客户端有没有访问这个资源id的权限,有则可以访问当前服务
        resources.resourceId(RESOURCE_ID)
                .tokenStore(tokenStore)
        ;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.sessionManagement() // 采用token进行管理身份,而没有采用session,所以不需要创建HttpSession
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests() // 请求的授权配置
                // 放行以 /api 开头的请求接口
                .antMatchers("/api/**").permitAll()
                // 所有请求都要有all范围权限
                .antMatchers("/**").access("#oauth2.hasScope('all')")
                // 其他请求都要通过身份认证
                .anyRequest().authenticated();
    }

}
原文地址:https://www.cnblogs.com/linding/p/14994324.html