springboot整合springsecurity+oauth2.0
本文采用的springboot去整合springsecurity,采用oauth2.0授權(quán)認(rèn)證,使用jwt對(duì)token增強(qiáng)。本文僅為學(xué)習(xí)記錄,如有不足多謝提出。
OAuth2 簡(jiǎn)介
OAuth 2.0是用于授權(quán)的行業(yè)標(biāo)準(zhǔn)協(xié)議。OAuth 2.0為簡(jiǎn)化客戶端開發(fā)提供了特定的授權(quán)流,包括Web應(yīng)用、桌面應(yīng)用、移動(dòng)端應(yīng)用等。文章來源:http://www.zghlxwxcb.cn/news/detail-444896.html
OAuth2 相關(guān)名詞解釋
- Resource owner(資源擁有者):擁有該資源的最終用戶,他有訪問資源的賬號(hào)密碼;
- Resource server(資源服務(wù)器):擁有受保護(hù)資源的服務(wù)器,如果請(qǐng)求包含正確的訪問令牌,可以訪問資源;
- Client(客戶端):訪問資源的客戶端,會(huì)使用訪問令牌去獲取資源服務(wù)器的資源,可以是瀏覽器、移動(dòng)設(shè)備或者服務(wù)器;
- Authorization server(認(rèn)證服務(wù)器):用于認(rèn)證用戶的服務(wù)器,如果客戶端認(rèn)證通過,發(fā)放訪問資源服務(wù)器的令牌。
四種授權(quán)模式
- Authorization Code(授權(quán)碼模式):正宗的OAuth2的授權(quán)模式,客戶端先將用戶導(dǎo)向認(rèn)證服務(wù)器,登錄后獲取授權(quán)碼,然后進(jìn)行授權(quán),最后根據(jù)授權(quán)碼獲取訪問令牌;
- Implicit(簡(jiǎn)化模式):和授權(quán)碼模式相比,取消了獲取授權(quán)碼的過程,直接獲取訪問令牌;
- Resource Owner Password Credentials(密碼模式):客戶端直接向用戶獲取用戶名和密碼,之后向認(rèn)證服務(wù)器獲取訪問令牌;
- Client Credentials(客戶端模式):客戶端直接通過客戶端認(rèn)證(比如client_id和client_secret)從認(rèn)證服務(wù)器獲取訪問令牌。
主要pom文件引入
<!-- springSecurity-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Oauth2-->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<!-- jwt增強(qiáng)-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
#本文采用的springboot版本為2.6.3,由于Spring Security 在 Spring Boot 2.7.0 中已棄用的 WebSecurityConfigurerAdapter
所有在配置。所以在配置SpringSecurity配置時(shí),原先configure采用bena配置SecurityFilterChain bean
編寫實(shí)體類
用戶類
package com.example.health.model;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Set;
/**
* 登錄用戶信息
*/
@Data
public class SecurityUser implements UserDetails {
/**
* 用戶id
*/
private Long userId;
/**
* 用戶名
*/
private String username;
/**
* 部門ID
*/
private Long deptId;
/**
* 用戶密碼
*/
private String password;
/**
* 用戶狀態(tài)
*/
private Boolean enabled;
/**
* 權(quán)限數(shù)據(jù)
*/
private Collection<SimpleGrantedAuthority> authorities;
/**
* 權(quán)限列表
*/
private Set<String> permissions;
public SecurityUser() {
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
/**
* 賬戶是否未過期,過期無法驗(yàn)證
*/
@JSONField(serialize = false)
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 指定用戶是否解鎖,鎖定的用戶無法進(jìn)行身份驗(yàn)證
*
* @return
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 指示是否已過期的用戶的憑據(jù)(密碼),過期的憑據(jù)防止認(rèn)證
*
* @return
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 是否可用 ,禁用的用戶不能身份驗(yàn)證
*
* @return
*/
@Override
public boolean isEnabled() {
return this.enabled;
}
}
auth2獲取Token返回信息封裝
package com.example.health.model.dto;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Oauth2獲取Token返回信息封裝
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public class Oauth2TokenDto {
/**
* 訪問令牌
*/
private String token;
/**
* 刷新令牌
*/
private String refreshToken;
/**
* 訪問令牌頭前綴
*/
private String tokenHead;
/**
* 有效時(shí)間(秒)
*/
private int expiresIn;
}
添加UserServiceImpl實(shí)現(xiàn)UserDetailsService接口,用于加載用戶信息:
package com.example.health.security.handle;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.health.common.constant.MessageConstant;
import com.example.health.mapper.SysUserMapper;
import com.example.health.model.SecurityUser;
import com.example.health.model.entity.SysUser;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.Objects;
@Service
public class UserServiceImpl implements UserDetailsService {
@Autowired
private SysUserMapper sysUserMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser sysUser = sysUserMapper.selectOne(new QueryWrapper<SysUser>().lambda().eq(SysUser::getUserName, username));
if (Objects.isNull(sysUser)) {
throw new UsernameNotFoundException(MessageConstant.USERNAME_PASSWORD_ERROR);
}
SecurityUser securityUser = new SecurityUser();
BeanUtils.copyProperties(sysUser, securityUser);
securityUser.setEnabled(!Objects.equals(0, sysUser.getStatus()));
return securityUser;
}
}
添加AuthenticationEntryPointImpl實(shí)現(xiàn)AuthenticationEntryPoint接口,用于處理失敗處理類 :
/**
* 認(rèn)證失敗處理類 返回未授權(quán)
*
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
int code = HttpStatus.UNAUTHORIZED;
String msg = String.format("請(qǐng)求訪問:%s,認(rèn)證失敗,無法訪問系統(tǒng)資源", request.getRequestURI());
ServletUtils.renderString(response, JSON.toJSONString(ResultUtils.error(code, msg)));
}
}
配置JWT內(nèi)容增強(qiáng)器
/**
* JWT內(nèi)容增強(qiáng)器
*/
@Component
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
Map<String, Object> info = new HashMap<>();
//把用戶ID設(shè)置到JWT中
info.put("user_id", securityUser.getUserId());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
}
添加認(rèn)證服務(wù)器配置,使用@EnableAuthorizationServer注解開啟:
/**
* 認(rèn)證服務(wù)配置
*/
@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
private PasswordEncoder passwordEncoder;
private UserServiceImpl userDetailsService;
/**
* 該對(duì)象用來支持 password 模式
*/
private AuthenticationManager authenticationManager;
private JwtTokenEnhancer jwtTokenEnhancer;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client-app")
.secret(passwordEncoder.encode("123456"))
.scopes("all")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(3600)
.refreshTokenValiditySeconds(86400);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> delegates = new ArrayList<>();
delegates.add(jwtTokenEnhancer);
delegates.add(accessTokenConverter());
enhancerChain.setTokenEnhancers(delegates); //配置JWT的內(nèi)容增強(qiáng)器
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService) //配置加載用戶信息的服務(wù)
.accessTokenConverter(accessTokenConverter())
.tokenEnhancer(enhancerChain);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setKeyPair(keyPair());
return jwtAccessTokenConverter;
}
@Bean
public KeyPair keyPair() {
//從classpath下的證書中獲取秘鑰對(duì)
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
}
}
添加SpringSecurity配置,允許認(rèn)證相關(guān)路徑的訪問及表單登錄:
/**
* SpringSecurity配置
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 認(rèn)證失敗處理類
*/
@Autowired
private AuthenticationEntryPointImpl unauthorizedHandler;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* anyRequest | 匹配所有請(qǐng)求路徑
* access | SpringEl表達(dá)式結(jié)果為true時(shí)可以訪問
* anonymous | 匿名可以訪問
* denyAll | 用戶不能訪問
* fullyAuthenticated | 用戶完全認(rèn)證可以訪問(非remember-me下自動(dòng)登錄)
* hasAnyAuthority | 如果有參數(shù),參數(shù)表示權(quán)限,則其中任何一個(gè)權(quán)限可以訪問
* hasAnyRole | 如果有參數(shù),參數(shù)表示角色,則其中任何一個(gè)角色可以訪問
* hasAuthority | 如果有參數(shù),參數(shù)表示權(quán)限,則其權(quán)限可以訪問
* hasIpAddress | 如果有參數(shù),參數(shù)表示IP地址,如果用戶IP和參數(shù)匹配,則可以訪問
* hasRole | 如果有參數(shù),參數(shù)表示角色,則其角色可以訪問
* permitAll | 用戶可以任意訪問
* rememberMe | 允許通過remember-me登錄的用戶訪問
* authenticated | 用戶登錄后可訪問
*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// CSRF禁用,因?yàn)椴皇褂胹ession
.csrf().disable()
// 禁用HTTP響應(yīng)標(biāo)頭
.headers().cacheControl().disable().and()
// 認(rèn)證失敗處理類
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 過濾請(qǐng)求
.authorizeRequests()
// 對(duì)于登錄login 注冊(cè)register 驗(yàn)證碼captchaImage 允許匿名訪問
.antMatchers("/login", "/register", "/captchaImage").permitAll()
.antMatchers("/all/**").permitAll()
// 靜態(tài)資源,可匿名訪問
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 除上面外的所有請(qǐng)求全部需要鑒權(quán)認(rèn)證
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
編寫登錄接口,以及測(cè)試接口
/**
* 自定義Oauth2獲取令牌接口
*/
@RestController
@RequestMapping("/oauth")
public class AuthController {
@Autowired
private TokenEndpoint tokenEndpoint;
/**
* Oauth2登錄認(rèn)證
*/
@RequestMapping(value = "/token", method = RequestMethod.POST)
public BaseResponse<Oauth2TokenDto> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException, HttpRequestMethodNotSupportedException {
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
Oauth2TokenDto oauth2TokenDto = Oauth2TokenDto.builder()
.token(oAuth2AccessToken.getValue())
.refreshToken(oAuth2AccessToken.getRefreshToken().getValue())
.expiresIn(oAuth2AccessToken.getExpiresIn())
.tokenHead("Bearer ").build();
return ResultUtils.success(oauth2TokenDto);
}
}
@RestController
@RequestMapping("/all")
public class AllController {
@GetMapping(value = "/getStr")
public BaseResponse<?> getStr() {
return ResultUtils.success(“All”);
}
}
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping(value = "/getStr")
public BaseResponse<?> getStr() {
return ResultUtils.success("test");
}
}
測(cè)試使用password授權(quán)方式
文章來源地址http://www.zghlxwxcb.cn/news/detail-444896.html
到了這里,關(guān)于springboot整合springsecurity+oauth2.0密碼授權(quán)模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!