赞
踩
这里忽略springcloud-alibaba的搭建,只讲springsecurity的部分。
把生成的密钥对文件放在resources目录下
认证服务器的代码结构图
引入spring-security和oauth2的必须包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<!--Oauth2-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!--Oauth2整合jwt-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
严格来说不需要特别配置,这里只讲两个:
①表示后发现的bean会覆盖之前相同名称的bean
spring:
main:
allow-bean-definition-overriding: true
②配置客户端的clientId、clientSecret(非必须),表示这个客户端的标识,资源服务器会根据这个标识去oauth_client_details找其可访问的资源(即resource_ids字段以及scope字段),实际开发中登录时可由前端(或第三方系统)传来。
auth:
clientId: c1
clientSecret: secret1
@Configuration @EnableAuthorizationServer public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { //对称密钥------改用非对称密钥 //private static final String SIGNING_KEY = "oauth"; @Resource private AuthenticationManager authenticationManager; @Resource private PasswordEncoder passwordEncoder; @Resource private AuthorizationCodeServices authorizationCodeServices; @Resource private JwtAccessTokenConverter accessTokenConverter; @Resource private TokenStore tokenStore; @Resource private ClientDetailsService clientDetailsService; /** * 将客户端信息存储到数据库 ,参照JdbcClientDetailsService源码里数据库建表 * oauth_client_details * @param dataSource * @return */ @Bean public ClientDetailsService clientDetailsService(DataSource dataSource) { JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource); ((JdbcClientDetailsService) clientDetailsService).setPasswordEncoder(passwordEncoder); return clientDetailsService; } /** * 配置一个客户端 * 既可以通过授权码获取令牌,也可以通过密码获取令牌(有四种方式) * * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(clientDetailsService); } //配置令牌访问端点 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager)//认证管理(密码模式需要) .authorizationCodeServices(authorizationCodeServices)//授权码服务 .tokenServices(tokenServices())//令牌管理服务 .allowedTokenEndpointRequestMethods(HttpMethod.POST);//允许post提交访问令牌 } /** * 设置授权码模式的授权码如何存取 * * @param dataSource * @return */ @Bean public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) { return new JdbcAuthorizationCodeServices(dataSource); } @Bean public AuthorizationServerTokenServices tokenServices() { DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setClientDetailsService(clientDetailsService);//客户端详情 tokenServices.setSupportRefreshToken(true);//支持刷新令牌 tokenServices.setTokenStore(tokenStore);//令牌存储策略 //令牌增强 使用jwt令牌 //使用jwt令牌来替代默认令牌,这样做的好处是携带默认令牌访问资源,每次都要通过授权服务来认证令牌是否有效, // 而jwt则可以做到资源服务中自己解析从而判断令牌的有效性;另外一个优势就是jwt令牌有更高的安全性, // 可以使用公钥和私钥进行加密和解密,不容易被破解。 TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter)); tokenServices.setTokenEnhancer(tokenEnhancerChain); tokenServices.setAccessTokenValiditySeconds(7200);//令牌默认有效期2小时 tokenServices.setRefreshTokenValiditySeconds(9600);//刷新令牌默认有效期 return tokenServices; } /** * 默认是InMemoryTokenStore * 也可以采用JdbcTokenStore,JwtTokenStore。 * * @return */ @Bean public TokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); } @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); //加载密钥 converter.setKeyPair(keyPair()); return converter; } /** * 读取本地密钥对 * @return */ @Bean public KeyPair keyPair(){ KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("macrosoft.jks"),"420188".toCharArray()); KeyPair keyPair = factory.getKeyPair("macrosoft", "420188".toCharArray()); return keyPair; } /** * 令牌的访问策略 * * @param security * @throws Exception */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("permitAll()")//oauth/token_key是公开 .checkTokenAccess("permitAll()") //oauth/check_token 已经验证了的客户端才能请求check_token 端点 .allowFormAuthenticationForClients();//表单认证(申请令牌) } }
@Configuration @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable()//跨域伪造请求无效 .authorizeRequests().antMatchers("/**").permitAll()//全部放开 .anyRequest().authenticated(); // 登陆设置 http.formLogin(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder()); } /** * 获取新的权限信息 * * @return */ @Bean public UserDetailsService userDetailsService() { return new UserDetailServiceImpl(); } /** * 配置密码的加密规则 * * @return */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 授权服务需要用到这个bean * * @return * @throws Exception */ @Bean @Override public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } /** * 忽略拦截url或静态资源文件夹 * * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers(HttpMethod.GET, "/favicon.ico", "/*.html", "/**/*.css", "/**/*.js"); } /** * 注入RestTemplate * @return */ @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
public class UserDetailServiceImpl implements UserDetailsService { @Resource private SysUserMapper sysUserMapper; @Resource private SysRoleMapper sysRoleMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserDetails userDetails = null; try { //根据username查询用户信息 List<SysUserEntity> sysUserList = sysUserMapper.selectByUserNameSysUsers(username); if (CollectionUtils.isEmpty(sysUserList)) { throw new UsernameNotFoundException("该用户不存在!"); } SysUserEntity sysUserEntity = sysUserList.get(0); //根据userId查询角色权限 String roles = sysRoleMapper.getRoleNameById(sysUserEntity.getId()); List<GrantedAuthority> authorities = new ArrayList<>(); String[] arrstr = roles.split(";"); if (ObjectUtils.isNotEmpty(arrstr)) { for (String role : arrstr) { authorities.add(new SimpleGrantedAuthority(role)); } } userDetails = new User(username , sysUserEntity.getPassword() , true , true , true , true , authorities); } catch (UsernameNotFoundException e) { e.printStackTrace(); } return userDetails; } }
@Override public RespResult<LoginResultDTO> login(LoginRequestDTO loginRequestDTO) { RespResult<LoginResultDTO> result = new RespResult<>(); ServiceTemplate.executeApiService(getClass(), AuthScenceEnum.AUTH_LOGIN_SCENCE, loginRequestDTO, result, request -> { String username = loginRequestDTO.getUsername(); String password = loginRequestDTO.getPassword(); //1.获取当前服务路径 ServiceInstance serviceInstance = loadBalancerClient.choose("macro-auth"); URI uri = serviceInstance.getUri(); String url = uri + "/auth/oauth/token"; //2.组装请求体参数 MultiValueMap<String, String> body = new LinkedMultiValueMap<>(); body.add("grant_type", "password"); body.add("username", username); body.add("password", password); //3.在请求头组装clientId和clientSecret MultiValueMap<String, String> headers = new LinkedMultiValueMap<>(); //注意:重点,客户端在登录时可以传入clientId、clientSecret(线下给到客户端),那么这样就可以在oauth_client_details配置该clientId的访问资源服务器了 //这里目前写死只有一个clientId、clientSecret(即c1和secret1),在oauth_client_details表中配置了c1对应的resource_ids字段的res1,res2,res3 //也就是说资源服务器标识为res1或res2或res3的,clientId为c1的皆可访问 headers.add("Authorization", getHttpBasic(clientId, clientSecret)); //4.组装最终请求参数 HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, headers); //5.发起登录请求接口 ResponseEntity<Map> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, Map.class); //6.获取返回数据 Map map = responseEntity.getBody(); boolean flag = (map == null || map.get("access_token") == null || map.get("refresh_token") == null || map.get("jti") == null); AssertUtil.assertFalse(flag, AuthErrorEnum.GET_ACCESS_TOKEN_FAIL); //7.封装返回数据 LoginResultDTO loginResultDTO = new LoginResultDTO(); loginResultDTO.setAccessToken((String) map.get("access_token")); loginResultDTO.setRefreshToken((String) map.get("refresh_token")); loginResultDTO.setJti((String) map.get("jti")); //8.数据存入redis redisService.setCacheValueForTimes(RedisKeyTemplate.redisUseUserNameAsKey(username), JSON.toJSONString(loginResultDTO),600, TimeUnit.SECONDS); result.setData(loginResultDTO); }); return result; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。