微服务 oauth2实现JWT登录的案例 授权、安全、资源服务器配置

目录

一、添加依赖

二、添加授权中心配置类

三、服务安全配置类

四、SQL静态类

五、登录逻辑的实现类

六、通过APIPost获取JWT

七、附加,密钥的获取

八、资源服务器的访问控制


一、添加依赖

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

这里主要的依赖就是oauth2。

配置文件如下:

server:
  port: 9999
spring:
  application:
    name: authorization-server
  cloud:
    nacos:
      discovery:
        server-addr: nacos-server:8848
        username: nacos
        password: nacos
  datasource:
    url: jdbc:mysql://mysql-server:3307/dragon-backstage?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&useSSL=false
    username: root
    password: xxx
    driver-class-name: com.mysql.cj.jdbc.Driver

二、添加授权中心配置类

这里继承授权中心的适配器,通过PasswordEncoder来加密密码,通过AuthenticationManager来进行验证管理,通过jwt实现时需要重写一下方法。

package com.dragonwu.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;


@EnableAuthorizationServer  //开启授权服务器功能
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;//验证管理器

    @Qualifier("userServiceDetailServiceImpl")
    @Autowired
    private UserDetailsService userDetailsService;


    /**
     * 添加第三方客户端
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("dragon-api") //第三方客户端名称
                .secret(passwordEncoder.encode("dragon-secret"))  //第三方客户端的密钥
                .scopes("all") //第三方客户端的授权范围
                .authorizedGrantTypes("password","refresh_token")//密码授权,通过refresh_token来为过期的token获取新的token数据
                .accessTokenValiditySeconds(7*24*3600)  //token的有效期
                .refreshTokenValiditySeconds(30*24*3600)  //refresh_token的有效期
                .and()
                .withClient("inside-app")
                .secret(passwordEncoder.encode("inside-secret"))
                .authorizedGrantTypes("client_credentials")
                .scopes("all")
                .accessTokenValiditySeconds(Integer.MAX_VALUE) ;
        super.configure(clients);
    }

    /**
     * 配置验证管理器,UserDetailService
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                .tokenStore(jwtTokenStore())    //采用JWT存储token
                .tokenEnhancer(jwtAccessTokenConverter());  //转换器,将数据转为json存储
        super.configure(endpoints);
    }

    private TokenStore jwtTokenStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter tokenConverter=new JwtAccessTokenConverter();

        //加载服务器的私钥
        ClassPathResource classPathResource=new ClassPathResource("dragonbackstage.jks");
        KeyStoreKeyFactory keyStoreKeyFactory=new KeyStoreKeyFactory(classPathResource,"dragonbackstage".toCharArray());
        tokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("dragonbackstage","dragonbackstage".toCharArray()));
        return tokenConverter;
    }

}

三、服务安全配置类

这里用到了SpringSecurity来保证服务器安全,具体实现是继承网络安全适配器,实例化好相应对象放到bean里。

package com.dragonwu.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;


@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests().anyRequest().authenticated();
    }

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

    /**
     * 密码加密
     * @return passwordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    public static void main(String[] args) {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode("123456");
        System.out.println(encode);
    }

}

四、SQL静态类

package com.dragonwu.constant;

/**
 * 登录的常量
 */
public class LoginConstant {

    /**
     * 后台管理人员
     */
    public static final  String ADMIN_TYPE = "admin_type" ;

    /**
     * 普通的用户
     */
    public static  final  String MEMBER_TYPE = "member_type" ;

    /**
     * 使用用户名查询后台用户
     */
    public static  final String QUERY_ADMIN_SQL=
            "SELECT `id`,`username`,`pass_word`,`user_status` FROM sys_user WHERE username=?";

    /**
     * 普通用户查询SQL
     */
    public static final String QUERY_MEMBER_SQL=
            "SELECT `id`,`pass_word`,`user_status` FROM `user` WHERE mobile=? or email=?";
}

五、登录逻辑的实现类

这里是登录业务类,通过继承UserDetailsService来实现,最终返回UserDetails对象。

package com.dragonwu.service.impl;


import com.dragonwu.constant.LoginConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.access.AuthorizationServiceException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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 org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;

@Service
public class UserServiceDetailServiceImpl implements UserDetailsService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        ServletRequestAttributes requestAttributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String loginType=requestAttributes.getRequest().getParameter("login_type");//区分是后台人员还是普通用户
        if(StringUtils.isEmpty(loginType)){
            throw new AuthorizationServiceException("登录类型不能为null");
        }
        UserDetails userDetails=null;
        try{
            switch (loginType){
                case LoginConstant.ADMIN_TYPE:
                    userDetails=loadSysUserByUsername(username);
                    break;
                case LoginConstant.MEMBER_TYPE:
                    userDetails=loadMemberUserByUsername(username);
                    break;
                default:
                    throw new AuthorizationServiceException("暂不支持的登录方式:"+loginType);
            }
        }catch (IncorrectResultSizeDataAccessException e){//我们的用户不存在
            throw new UsernameNotFoundException("用户名"+username+"不存在");
        }
        return userDetails;
    }

    /**
     * 后台管理员登录
     * @return userDetails
     */
    private UserDetails loadSysUserByUsername(String username){
        //1、使用用户名查询用户
        //2、查询这个用户对应的权限
        //3、封装成一个UserDetails对象,返回
        return null;
    }

    /**
     * 普通用户登录
     * @return userDetatils
     */
    private UserDetails loadMemberUserByUsername(String username){
        return jdbcTemplate.queryForObject(LoginConstant.QUERY_MEMBER_SQL, new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet resultSet, int i) throws SQLException {
                //判断用户是否存在
                if(resultSet.wasNull()){
                    throw new UsernameNotFoundException("用户:"+username+"不存在");
                }
                long id=resultSet.getLong("id");//用户的id
                String password=resultSet.getString("pass_word");//普通用户的登录密码
                int status=resultSet.getInt("user_status");//会员的状态
                return new User(
                        String.valueOf(id),
                        password,
                        status==1,
                        true,
                        true,
                        true,
                        Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))
                );
            }
        },username,username);
    }
}

六、通过APIPost获取JWT

这里填写你的基础认证名称和密码。

添加一下参数。

获取到token。

JWT已经获取到了。

也可以通过refresh_token来对旧的token进行刷新

 下面开始获取新的token值:

 获取新的token成功:

模拟内置服务器之间的访问:

 

 访问结果,此时获取到的token是没有用户信息的:

七、附加,密钥的获取

 打开cmd输入如下:

keytool -genkeypair -alias dragonbackstage -keyalg RSA -keypass dragonbackstage -keystore dragonbackstage.jks -validity 365 -storepass dragonbackstage

 之后会生成密钥。

获取到密钥以后,再输入一下内容

keytool -list -rfc --keystore dragonbackstage.jks | openssl x509 -inform pem -pubkey

便可以获取到公钥。

交私钥放到resource目录下即可:

八、资源服务器的访问控制

添加资源服务器的配置如下:

package com.dragonwu.config.resource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.util.FileCopyUtils;

import java.nio.charset.StandardCharsets;


@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf()
                .disable()
                .sessionManagement().disable()
                .authorizeRequests()
                .antMatchers(
                        "/column/test",
                        "/admin/login",
                        "/swagger-resources/configuration/ui",//用来获取支持的动作
                        "/swagger-resources",//用来获取api-docs的URI
                        "/swagger-resources/configuration/security",//安全选项
                        "/swagger-ui.html"
                ).permitAll()
                .antMatchers("/**").authenticated()
                .and().headers().cacheControl();
    }

    /**
     * 设置公钥
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(jwtTokenStore());

    }

    private TokenStore jwtTokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean // 放在ioc容器的
    public JwtAccessTokenConverter accessTokenConverter() {
        //resource 验证token(公钥) authorization 产生 token (私钥)
        JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
        String s = null;
        try {
            ClassPathResource classPathResource = new ClassPathResource("dragonbackstage.txt");
            byte[] bytes = FileCopyUtils.copyToByteArray(classPathResource.getInputStream());
            s = new String(bytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
        }
        tokenConverter.setVerifierKey(s);
        return tokenConverter;
    }
}

这里的公钥和之前的密钥是一对,放到resource目录下:

有了这样一个配置类,访问就可以被控制了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值