JavaEE:SpringSecurity实现Oauth2认证

说明:

启动mysql服务,启动一个eureka server端

一、工程配置:

1.导入security与oauth2等一些依赖包:

<dependencies>
    <!--导入mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--导入web依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--导入eureka客户端依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--导入security与oauth2依赖-->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-data</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-security</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-actuator</artifactId>
    </dependency>
    <!--导入JWT依赖-->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.0</version>
    </dependency>
</dependencies>

2.配置认证证书和其他信息,在工程application.yml中:

server:
  port: 10001
spring:
  application:
    name: token_server  #指定获取token的服务名称
  datasource: #配置mysql数据源(必须配置,不然报错)
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC
    username: root
    password: root
eureka: #配置连接eureka服务端(必须配置,不然报错)
  instance:
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://127.0.0.1:10000/eureka/  #eureka server端地址,启动一个eureka server端
auth2: #自定义配置,用于AuthConfig类和LoginBiz类中获取
  client-id: mallapp
  client-secret: mallsecret
encrypt: #配置jks证书信息,自动读取
  key-store:
    location: classpath:/mall.jks  #此证书为手动创建,放在resources目录下
    secret: mallsecret
    alias: mall
    password: mall123456

3.创建SpringApplication启动类:

@SpringBootApplication
@EnableDiscoveryClient
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
    @Bean(name = "restTemplate") //请求工具类
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

二、创建自动加载的配置类:

1.创建配置授权服务器端与客户端信息的类:

@Configuration
@EnableAuthorizationServer //自动加载,配置证书等信息
class AuthConfig extends AuthorizationServerConfigurerAdapter {
    //配置证书
    @Bean("key1")
    public KeyProperties keyProperties() {
        return new KeyProperties();
    }
    @Resource(name = "key1")
    private KeyProperties keyProperties;
    //配置Token转换类
    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(AuthToMap authToMap) {
        JwtAccessTokenConverter conver = new JwtAccessTokenConverter();
        KeyPair key = new KeyStoreKeyFactory(keyProperties.getKeyStore().getLocation(),   //配置证书路径
                keyProperties.getKeyStore().getSecret().toCharArray())                    //配置证书秘钥
                .getKeyPair(keyProperties.getKeyStore().getAlias(),                       //配置证书别名
                        keyProperties.getKeyStore().getPassword().toCharArray());         //配置证书密码
        conver.setKeyPair(key);
        ((DefaultAccessTokenConverter) conver.getAccessTokenConverter()).setUserTokenConverter(authToMap); //配置为自定义的AuthToMap转换类
        return conver;
    }
    //配置token读/写存储类
    @Autowired
    private TokenStore tokenStore;
    @Bean
    @Autowired
    public TokenStore tokenStore(JwtAccessTokenConverter conver) {
        return new JwtTokenStore(conver);
    }
    //授权管理类
    @Autowired
    private AuthenticationManager authenticationManager;
    //授权认证类,重写loadUserByUsername方法,用于封装为User返回
    @Autowired
    private UserDetailsService userDetailsService;
    //自定义的AuthToMap转换类
    @Autowired
    private AuthToMap authToMap;
    @Value("${auth2.client-id}") //application.yml中自定义字段,用于组装头字段
    private String clientId;
    @Value("${auth2.client-secret}") //application.yml中自定义字段,用于组装头字段
    private String clientSecret;
    @Override //配置客户端访问的一些信息
    public void configure(ClientDetailsServiceConfigurer clientConfig) throws Exception {
        clientConfig.inMemory()
                .withClient(clientId)                      //配置客户端id,与application.yml一样
                .secret(clientSecret)                      //配置秘钥,与application.yml一样
                .redirectUris("http://www.baidu.com")      //配置跳转地址
                .accessTokenValiditySeconds(24 * 60 * 60)  //配置token有效期
                .refreshTokenValiditySeconds(24 * 60 * 60) //配置refreshToken有效期
                .authorizedGrantTypes("authorization_code",//根据授权码生成令牌
                        "client_credentials",              //客户端认证
                        "refresh_token",                   //刷新token
                        "password")                        //设置为密码认证方式
                .scopes("app", "web");                     //设置客户端范围
    }
    @Override //配置授权服务器端的一些信息
    public void configure(AuthorizationServerEndpointsConfigurer serverConfig) throws Exception {
        serverConfig.accessTokenConverter(jwtAccessTokenConverter)  //配置Token转换类
                .authenticationManager(authenticationManager)       //配置授权管理类
                .tokenStore(tokenStore)                             //配置token读写存储类
                .userDetailsService(userDetailsService);            //配置授权认证类,重写loadUserByUsername方法,用于封装为User返回
    }
    @Override//配置授权服务器的安全信息
    public void configure(AuthorizationServerSecurityConfigurer serverSecurity) throws Exception {
        serverSecurity.allowFormAuthenticationForClients()
                .passwordEncoder(new BCryptPasswordEncoder()) //配置密码加密方式
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }
}

2.创建配置验证规则类,设置不拦截的URL:

@EnableWebSecurity
@Order(-1)
@Configuration //配置验证规则,例外URL(自动加载)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override //配置不拦截的请求
    public void configure(WebSecurity ws) throws Exception {
        ws.ignoring().antMatchers("/login", "/logout"); //不拦截登录与登出的请求路径
    }
    @Override //配置验证规则
    public void configure(HttpSecurity hs) throws Exception {
        hs.csrf().disable().httpBasic()  //使用HttpBasic认证:见HttpBasicConfigurer
                .and().formLogin()       //使用表单验证:见FormLoginConfigurer
                .and().authorizeRequests()   //Url授权配置器,请求除例外的URL需要权限:见ExpressionUrlAuthorizationConfigurer
                .anyRequest() //任何请求:见AnyRequestMatcher
                .authenticated();       //其他请求都需要经过验证

    }
    @Bean//加密工具类
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
    }
}

3.创建信息转换类,将Auth转为Map返回:

@Component //信息转换类,将Auth转为Map返回
public class AuthToMap extends DefaultUserAuthenticationConverter {
    @Autowired//授权认证类,重写loadUserByUsername方法,用于封装为User返回
    UserDetailsService userDetailsService;
    @Override //将Auth转为Map
    public Map<String, ?> convertUserAuthentication(Authentication auth) {
        User user = null;  //org.springframework.security.core.userdetails.User
        if (auth.getPrincipal() != null && auth.getPrincipal() instanceof User) {
            user = (User) auth.getPrincipal();
        } else {//调用自定义授权认证类,获取用户信息
            user = (User) userDetailsService.loadUserByUsername(auth.getName());
        }
        //将信息封装为Map返回
        Map<String, Object> map = new LinkedHashMap();
        map.put("username", user.getUsername());
        map.put("password", user.getPassword());
        if (auth.getAuthorities() != null && !auth.getAuthorities().isEmpty()) {
            map.put("authorities", AuthorityUtils.authorityListToSet(auth.getAuthorities()));
        }
        return map;
    }
}

4.创建自定义授权认证类,重写loadUserByUsername方法,用于封装为User返回:

@Service //授权认证类,重写loadUserByUsername方法,用于封装为User返回
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
    @Autowired //org.springframework.security.oauth2.provider.ClientDetailsService;
    private ClientDetailsService clientDetailsService;
    @Override //重写loadUserByUsername方法,修改认证规则
    public UserDetails loadUserByUsername(String phone) throws UsernameNotFoundException {
        if (SecurityContextHolder.getContext().getAuthentication() == null) { //没有认证
            ClientDetails details = clientDetailsService.loadClientByClientId(phone); //根据手机号加载client-secret信息
            if (details != null) {
                return new User(phone,
                        new BCryptPasswordEncoder().encode(details.getClientSecret()), //对client-secret加密
                        AuthorityUtils.commaSeparatedStringToAuthorityList(""));
            }
        }
        //org.springframework.security.core.userdetails.User
        return new User(phone,
                new BCryptPasswordEncoder().encode("mall123456"), //对密码加密
                AuthorityUtils.commaSeparatedStringToAuthorityList("user_info"));
    }
}

三、使用oauth2实现登录返回token:

1.响应给客户端的json实体类:

//响应给客户端的json实体类
public class LoginInfo {
    public String token;  //访问其他接口时作为登录标识
    public String refreshToken; //过期时刷薪token时用
    public String jti; //身份标识
    public LoginInfo(String token, String refreshToken, String jti) {
        this.token = token;
        this.refreshToken = refreshToken;
        this.jti = jti;
    }
}

2.登录业务类,请求授权服务,获取token:

@Service //登录业务类
public class LoginBiz {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private LoadBalancerClient loadBalancerClient;
    @Value("${auth2.client-id}") //application.yml中自定义字段,用于组装头字段
    private String clientId;
    @Value("${auth2.client-secret}") //application.yml中自定义字段,用于组装头字段
    private String clientSecret;
    public LoginInfo login(String phone, String pwd) {
        //1.创建请求头字段和参数
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();  //MultiValueMap继承了Map
        params.add("grant_type", "password"); //设置为密码授权方式
        params.add("username", phone); //手机号
        params.add("password", pwd);   //登录密码
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        headers.add("Authorization", "Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes())); //Authorization认证
        //2.处理400或401的情况
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
                if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401) {
                    super.handleError(response);
                }
            }
        });
        String url = loadBalancerClient.choose("token_server").getUri().toString() + "/oauth/token";
        System.out.println("    url: " + url);
        //3.请求获取token信息
        Map<String, String> body = restTemplate.exchange(
                url,   //获取token的服务地址,auth2_server为application.yml中的spring.application.name值
                HttpMethod.POST, //POST请求方式
                new HttpEntity<MultiValueMap>(params, headers),  //设置请求参数和头字段
                Map.class).getBody();
        if (body == null) {
            return null;
        }
        String token = body.get("access_token");  //访问其他接口时作为登录标识
        String refresh_token = body.get("refresh_token"); //刷薪token时用
        String jti = body.get("jti"); //身份标识
        return new LoginInfo(token, refresh_token, jti);
    }
}

3.登录请求拦截处理类:

@Controller  //登录请求拦截处理类
public class LoginControl {
    @Autowired //登录业务类
    private LoginBiz loginBiz;
    @RequestMapping("/login")
    @ResponseBody
    public LoginInfo login(String phone, String pwd) {
        return loginBiz.login(phone, pwd);
    }
}

4.请求登录接口查看效果(返回带token的json串表示成功了):

http://127.0.0.1:10001/login?phone=xxx&pwd=mall123456

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值