SpringSecurity+Oauth2+JWT
快速入門
1. 創(chuàng)建基礎項目
file ==> new ==> project ==> Spring Initializr ==>next ==> web(Spring Web)、Security(Spring Security) ==>一直下一步
2. 編寫代碼進行測試
創(chuàng)建controller
@Controller
public class LoginController {
@RequestMapping("login")
public String login(){
System.out.println("登入成功!");
return "redirect:main.html";
}
}
static下創(chuàng)建login.html、main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
用戶名:<input type="text" name="username"/><br/>
密碼:<input type="password" name="password"/><br/>
<input type="submit" value="登入" />
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登入成功!??!
</body>
</html>
3. 啟動項目進行測試
訪問http://localhost:8080/login.html
會進入SpringSecurity框架自帶的登入頁面 用戶默認user 密碼控制臺生成 輸入賬號密碼跳轉(zhuǎn)到自己定義的login.html頁面證明項目搭建完成
自定義登入
1. 定義SecurityConfig
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
}
2. 定義UserDetailService實現(xiàn)類
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder pw;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根據(jù)用戶名從數(shù)據(jù)庫中取出用戶 這里暫時寫死
if (!"admin".equals(username)){
throw new UsernameNotFoundException("用戶:"+username+"不存在");
}
//獲取用戶的密碼 也暫時寫死
String root = pw.encode("root");
return new User(username,root, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
}
}
3. 編寫controller
@RequestMapping("toMain")
public String toMain(){
return "redirect:main.html";
}
@RequestMapping("toError")
public String toError(){
return "redirect:error.html";
}
4. 定義SecurityConfig類
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//表單提交
http.formLogin()
//當訪問/login時認為是登入,執(zhí)行l(wèi)oadUserByUsername方法
.loginProcessingUrl("/login")
//自定義登入頁面
.loginPage("/login.html")
//登入成功的頁面
.successForwardUrl("/toMain")
//登入失敗
.failureForwardUrl("/toError")
;
/**
* anyRequest | 匹配所有請求路徑
* access | SpringEl表達式結果為true時可以訪問
* anonymous | 匿名可以訪問
* denyAll | 用戶不能訪問
* fullyAuthenticated | 用戶完全認證可以訪問(非remember-me下自動登錄)
* hasAnyAuthority | 如果有參數(shù),參數(shù)表示權限,則其中任何一個權限可以訪問
* hasAnyRole | 如果有參數(shù),參數(shù)表示角色,則其中任何一個角色可以訪問
* hasAuthority | 如果有參數(shù),參數(shù)表示權限,則其權限可以訪問
* hasIpAddress | 如果有參數(shù),參數(shù)表示IP地址,如果用戶IP和參數(shù)匹配,則可以訪問
* hasRole | 如果有參數(shù),參數(shù)表示角色,則其角色可以訪問
* permitAll | 用戶可以任意訪問
* rememberMe | 允許通過remember-me登錄的用戶訪問
* authenticated | 用戶登錄后可訪問
*/
//授權認證
http.authorizeRequests()
.antMatchers("/login.html","/error.html").permitAll()
//除上述請求任何請求都需要認證
.anyRequest().authenticated();
//關閉csrf防護
http.csrf().disable();
}
@Bean
//強散列哈希加密實現(xiàn)
public PasswordEncoder getPw() {
return new BCryptPasswordEncoder();
}
}
自定義登入成功失敗處理器
1. 定義AuthenticationSuccessHandler接口實現(xiàn)類、AuthenticationFailureHandler接口實現(xiàn)類
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String url;
public MyAuthenticationSuccessHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.sendRedirect(url);
}
}
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String url;
public MyAuthenticationFailureHandler(String url) {
this.url = url;
}
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.sendRedirect(url);
}
}
2. 修改SecurityConfig配置類
//表單提交
http.formLogin()
//當訪問/login時認為是登入,執(zhí)行l(wèi)oadUserByUsername方法
.loginProcessingUrl("/login")
//自定義登入頁面
.loginPage("/login.html")
// //登入成功的頁面
// .successForwardUrl("/toMain")
//自定義登入成功處理器 不能與successForwardUrl同時使用否側(cè)報錯
.successHandler(new MyAuthenticationSuccessHandler("/main.html"))
// //登入失敗
// .failureForwardUrl("/toError")
.failureHandler(new MyAuthenticationFailureHandler("/error.html"));
權限判斷
1. 根據(jù)權限進行過濾
//如果有參數(shù),參數(shù)表示權限,則其中任何一個權限可以訪問
.antMatchers("/main1.html").hasAnyAuthority("demo")
//如果有參數(shù),參數(shù)表示權限,則其權限可以訪問
.antMatchers("/main1.html").hasAuthority("demo")
2. 根據(jù)角色進行過濾
//如果有參數(shù),參數(shù)表示角色,則其中任何一個角色可以訪問
.antMatchers("/main1.html").hasAnyRole("test")
//如果有參數(shù),參數(shù)表示角色,則其角色可以訪問
.antMatchers("/main1.html").hasRole("test")
注意:配置角色時要以ROLE_為前綴
3. 根據(jù)ip進行過濾
.antMatchers("/main1.html").hasIpAddress("127.0.0.1")
自定義異常返回
1. 創(chuàng)建AccessDeniedHandler接口實現(xiàn)類
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
//設置響應狀態(tài)碼
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setHeader("Content-Type","application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write("{\"status\":\"error\",\"msg\":\"權限不足,請聯(lián)系管理員\"}");
writer.flush();
writer.close();
}
}
2. 配置SecurityConfig配置類
//異常處理
http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);
自定義方法實現(xiàn)權限控制
1. 編寫接口
public interface MyService {
boolean hasPermission(HttpServletRequest request, Authentication authentication);
}
2. 實現(xiàn)接口
@Service
public class MyServiceImpl implements MyService {
@Override
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
//獲取當前用戶
Object o = authentication.getPrincipal();
if (o instanceof UserDetails){
UserDetails userDetails = (UserDetails) o;
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
//判斷擁有權限是否包含
return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
}
return false;
}
}
3. 修改SecurityConfig配置類
.anyRequest().access("@myServiceImpl.hasPermission(request,authentication)");
注解實現(xiàn)權限控制
1. 啟動類開啟注解
@EnableGlobalMethodSecurity(securedEnabled = true)
2. controller接口上加注解
//擁有ROLE_test角色權限的用戶可以訪問
//@Secured("ROLE_test")
@PreAuthorize("hasRole('ROLE_test')")
@RequestMapping("toMain1")
public String toMain1(){
return "redirect:main1.html";
}
記住我實現(xiàn)
1. 所需依賴
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
2. 配置數(shù)據(jù)源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false&rewriteBatchedStatements=true
username: root
password: root
3. 配置SecurityConfig配置類
配置所需bean
@Bean
public PersistentTokenRepository getPersistentTokenRepository(){
JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
repository.setDataSource(dataSource);
//自動建表 建完表注釋掉
repository.setCreateTableOnStartup(true);
return repository;
}
@Autowired
private UserDetailServiceImpl userDetailServiceImpl;
@Autowired
private DataSource dataSource;
@Autowired
private PersistentTokenRepository persistentTokenRepository;
configure()方法添加
//記住我
http.rememberMe()
//設置過期時間 單位秒 默認兩周
.tokenValiditySeconds(30)
.userDetailsService(userDetailServiceImpl)
.tokenRepository(getPersistentTokenRepository())
//設置參數(shù)名稱
// .rememberMeParameter()
;
login.html新增記住我
<form action="/login" method="post">
用戶名:<input type="text" name="username"/><br/>
密碼:<input type="password" name="password"/><br/>
記住我:<input type="checkbox" name="remember-me" value="true" /><br/>
<input type="submit" value="登入" />
</form>
退出登入
1. 修改main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登入成功?。?!<a href="/toMain1">跳轉(zhuǎn)</a> <a href="/logout">退出</a>
</body>
</html>
2. 配置SecurityConfig配置類
//退出登入
http.logout().logoutSuccessUrl("/login.html");
CSRF
CSRF 利用的是網(wǎng)站對用戶網(wǎng)頁瀏覽器的信任??缯菊埱蠊?,簡單地說,是攻擊者通過一些技術手段欺騙用戶的瀏覽器去訪問一個自己曾經(jīng)認證過的網(wǎng)站并運行一些操作
默認開啟生成一個類似于token的碼進行身份識別
Oauth2認證
1. Oauth2認證流程:
- 客戶端請求資源擁有者授權
- 資源擁有者返回授權碼
- 客戶端拿到授權碼請求驗證服務申請令牌
- 拿到令牌申請資源服務返回受保護資源
2. 術語
- 客戶憑證:客戶端的clientId和密碼用于認證客戶
- 令牌:授權服務器接收到客戶端請求辦法的令牌
- 作用域:客戶端請求訪問令牌時,由資源擁有者額外指定的細分權限
- 授權碼:僅用于授權碼授權類型,用于獲取訪問令牌和刷新令牌
- 訪問令牌:相當于門禁卡,有了這個門禁卡就可以直接訪問
- 刷新令牌:相當于刷新門禁卡的過期時間
- BearerToken:不管誰拿到Token都可以訪問資源
- Proof of Possession Token:可以校驗client是否對Token有明確的使用權
3. 四種模式
授權碼模式、簡易授權碼模式、密碼模式、客戶端模式
Oauth2入門授權碼模式
**1. ** 創(chuàng)建springboot項目 注入相關依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.csf</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security-oauth2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. 創(chuàng)建定義登入
public class User implements UserDetails {
private String username;
private String password;
public List<GrantedAuthority> authorities;
public User(String username, String password, List<GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
}
@Override
public List<GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return password;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
@Service
public class UserService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
String password = passwordEncoder.encode("123456");
return new User("admin",password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("getUser")
public Object getUser(Authentication authentication) {
return authentication.getPrincipal();
}
}
3. 創(chuàng)建SecurityConfig配置類
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder getPas() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/oauth/**","/login/**","/logout/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
;
}
}
4. 創(chuàng)建授權碼服務配置類
/**
* 授權碼服務
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置用戶名
.withClient("admin")
//配置密碼
.secret(passwordEncoder.encode("112233"))
//配置Token訪問有效期 單位秒
.accessTokenValiditySeconds(3600)
//配置授權成功跳轉(zhuǎn)路徑
.redirectUris("http://www.baidu.com")
//配置申請權限
.scopes("all")
//配置授權類型 authorization_code 授權碼模式
.authorizedGrantTypes("authorization_code")
;
}
}
5. 創(chuàng)建資源服務配置類
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//所有請求攔截
.anyRequest().authenticated()
.and()
.requestMatchers()
//放行
.antMatchers("/user/**");
}
}
6. 啟動項目訪問獲取授權碼
http://localhost:8902/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all
7. 拿到授權碼訪問http://localhost:8902/oauth/token 獲取token
BasicAuth | admin | 112233 |
---|---|---|
Boby | grant_type | authorization_code |
code | 授權碼 | |
client_id | admin | |
redirect_uri | http://www.baidu.com | |
scope | all | |
Oauth2密碼模式
1. SecurityConfig配置類新增bean
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
2. 修改AuthorizationServerConfig配置類
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置用戶名
.withClient("admin")
//配置密碼
.secret(passwordEncoder.encode("112233"))
//配置Token訪問有效期 單位秒
.accessTokenValiditySeconds(3600)
//配置授權成功跳轉(zhuǎn)路徑
.redirectUris("http://www.baidu.com")
//配置申請權限
.scopes("all")
//配置授權類型 authorization_code 授權碼模式 password密碼模式
.authorizedGrantTypes("password")
;
}
/**
* 密碼模式所需配置
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService);
}
}
3. 測試 訪問http://localhost:8902/oauth/token
BasicAuth | admin | 112233 |
---|---|---|
Boby | grant_type | password |
scope | all | |
username | admin | |
password | 123456 | |
4. token存入redis
配置redis
spring:
redis:
host: 127.0.0.1
port: 6379
創(chuàng)建redis配置類
@Configuration
public class RedisConfig {
@Resource
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore getTokenStore(){
return new RedisTokenStore(redisConnectionFactory);
}
}
修改AuthorizationServerConfig
@Resource(name = "getTokenStore")
private TokenStore tokenStore;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService)
.tokenStore(tokenStore);
}
Jwt入門
1. 注入相關依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2. 編寫測試類
void creatJwt() {
long l = System.currentTimeMillis();
l = l+(1*60*1000);
JwtBuilder jwtBuilder = Jwts.builder()
//聲明標識
.setId("8888")
//主體
.setSubject("Rose")
//創(chuàng)建日期
.setIssuedAt(new Date())
//算法,鹽
.signWith(SignatureAlgorithm.HS256, "xxxx")
//設置過期時間
.setExpiration(new Date(l))
//自定義
.claim("name","常")
.claim("mobile","999999999")
;
String token = jwtBuilder.compact();
System.out.println(token);
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
String[] split = token.split("\\.");
System.out.println(Base64Codec.BASE64.decodeToString(split[0]));
System.out.println(Base64Codec.BASE64.decodeToString(split[1]));
System.out.println(Base64Codec.BASE64.decodeToString(split[2]));
}
3. token解密
void parsingToken(){
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODg4Iiwic3ViIjoiUm9zZSIsImlhdCI6MTY2NjMyMzYyMywiZXhwIjoxNjY2MzIzNjgzLCJuYW1lIjoi5bi4IiwibW9iaWxlIjoiOTk5OTk5OTk5In0.Hvmb8-qZydOwpM9yTI07EDxNxqQ06I2DAVyiYVQnlzY";
Claims claims = Jwts.parser().setSigningKey("xxxx").parseClaimsJws(token).getBody();
String id = claims.getId();
String subject = claims.getSubject();
Date issuedAt = claims.getIssuedAt();
Date expiration = claims.getExpiration();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(id);
System.out.println(subject);
System.out.println(format.format(issuedAt));
System.out.println(format.format(expiration));
System.out.println(format.format(new Date()));
System.out.println(claims.get("name"));
System.out.println(claims.get("mobile"));
}
Oauth2整合Jwt
1. 編寫Jwt配置類
@Configuration
public class JwtTokenStoreConfig {
@Bean
public TokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//配置密鑰
converter.setSigningKey("test_ket");
return converter;
}
}
2. 修改授權碼服務配置類
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
@Resource(name = "jwtTokenStore")
private TokenStore tokenStore;
@Resource(name = "jwtAccessTokenConverter")
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置用戶名
.withClient("admin")
//配置密碼
.secret(passwordEncoder.encode("112233"))
//配置Token訪問有效期 單位秒
// .accessTokenValiditySeconds(3600)
//配置授權成功跳轉(zhuǎn)路徑
// .redirectUris("http://www.baidu.com")
//配置申請權限
.scopes("all")
//配置授權類型 authorization_code 授權碼模式 password密碼模式
.authorizedGrantTypes("password")
;
}
/**
* 密碼模式所需配置
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService)
.tokenStore(tokenStore)
.accessTokenConverter(jwtAccessTokenConverter)
;
}
}
Jwt擴展存儲內(nèi)容
1. 創(chuàng)建jwt增強器
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
HashMap<String, Object> map = new HashMap<>();
map.put("name","常");
((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(map);
return oAuth2AccessToken;
}
}
2. 修改Jwt配置類
@Configuration
public class JwtTokenStoreConfig {
@Bean
public TokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//配置密鑰
converter.setSigningKey("test_ket");
return converter;
}
@Bean
public JwtTokenEnhancer jwtTokenEnhancer(){
return new JwtTokenEnhancer();
}
}
3. 修改AuthorizationServerConfig配置類
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
@Resource(name = "jwtTokenStore")
private TokenStore tokenStore;
@Resource(name = "jwtAccessTokenConverter")
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//配置用戶名
.withClient("admin")
//配置密碼
.secret(passwordEncoder.encode("112233"))
//配置Token訪問有效期 單位秒
// .accessTokenValiditySeconds(3600)
//配置授權成功跳轉(zhuǎn)路徑
// .redirectUris("http://www.baidu.com")
//配置申請權限
.scopes("all")
//配置授權類型 authorization_code 授權碼模式 password密碼模式
.authorizedGrantTypes("password")
;
}
/**
* 密碼模式所需配置
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//jwt配置增強器
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
ArrayList<TokenEnhancer> list = new ArrayList<>();
list.add(jwtTokenEnhancer);
list.add(jwtAccessTokenConverter);
enhancerChain.setTokenEnhancers(list);
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService)
.tokenStore(tokenStore)
.accessTokenConverter(jwtAccessTokenConverter)
.tokenEnhancer(enhancerChain)
;
}
}
Jwt解析
1. 注入依賴
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
2. 測試
@RequestMapping("getUser")
public Object getUser(HttpServletRequest request) {
String header = request.getHeader("Authentication");
String[] split = header.split("Bearer ");
String token = split[1];
return Jwts.parser().setSigningKey("test_ket".getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token).getBody();
}
SpringSecurityOauth2整合SSO
1. 創(chuàng)建新springboot項目注入依賴
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
</dependencies>
Security+Jwt
1. 創(chuàng)建springboot工程注入相關依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.csf</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security-jwt</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--springSecurity跟jwt的依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--測試-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--mybatis‐plus的springboot支持-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<!--mysql驅(qū)動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<!--mysql依賴包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--通過lombok包,實體類中不需要再寫set,get方法,只需要添加一個@Data注解即可-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. 配置數(shù)據(jù)源
server:
port: 9001
logging:
level:
root: debug
spring:
datasource:
password: root
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_german2_ci DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_german2_ci DEFAULT NULL,
`role` varchar(255) CHARACTER SET utf8 COLLATE utf8_german2_ci DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_german2_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
3. 創(chuàng)建JwtUser
public class JwtUser implements UserDetails {
private Integer id;
private String username;
private String password;
public Collection<? extends GrantedAuthority> authorities;
//傳入User構建JwtUser
public JwtUser(User user) {
id = user.getId();
username = user.getUsername();
password = user.getPassword();
//權限
authorities = Collections.singleton(new SimpleGrantedAuthority(user.getRole()));
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
4. JwtUtil
package com.csf.springsecurityjwt.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtUtils {
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
// TOKEN 過期時間
public static final long EXPIRATION = 1000 * 60 * 30; // 三十分鐘
public static final String APP_SECRET_KEY = "secret";
private static final String ROLE_CLAIMS = "rol";
/**
* 生成token
*
* @param username
* @param role
* @return
*/
public static String createToken(String username, String role) {
Map<String, Object> map = new HashMap<>();
map.put(ROLE_CLAIMS, role);
String token = Jwts
.builder()
.setSubject(username)
.setClaims(map)
.claim("username", username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS256, APP_SECRET_KEY).compact();
return token;
}
/**
* 獲取當前登錄用戶用戶名
*
* @param token
* @return
*/
public static String getUsername(String token) {
Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
return claims.get("username").toString();
}
/**
* 獲取當前登錄用戶角色
*
* @param token
* @return
*/
public static String getUserRole(String token) {
Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
return claims.get("rol").toString();
}
/**
* 獲解析token中的信息
*
* @param token
* @return
*/
public static Claims checkJWT(String token) {
try {
final Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
return claims;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 檢查token是否過期
*
* @param token
* @return
*/
public static boolean isExpiration(String token) {
Claims claims = Jwts.parser().setSigningKey(APP_SECRET_KEY).parseClaimsJws(token).getBody();
return claims.getExpiration().before(new Date());
}
}
5. 創(chuàng)建domain、mapper、service、controller
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String username;
private String password;
private String role;
}
@Data
public class UserDto implements Serializable {
private String username;
private String password;
}
public interface UserMapper extends BaseMapper<User> {
}
public interface IUserService extends IService<User> {
User findByUsername(String username);
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public User findByUsername(String username) {
LambdaQueryWrapper<User> wrapper = new QueryWrapper<User>().lambda();
wrapper.eq(User::getUsername,username);
return getOne(wrapper);
}
}
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userServiceImpl;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@PostMapping("saveUser")
//添加賬戶
public User saveUser(@RequestBody UserDto dto){
User user = new User();
BeanUtils.copyProperties(dto, user);
user.setPassword(bCryptPasswordEncoder.encode(dto.getPassword()));
//判斷是否是admin賬戶
if("admin".equals(dto.getUsername())){
user.setRole("ADMIN");
}else {
user.setRole("USER");
}
userServiceImpl.save(user);
return user;
}
@GetMapping("getUserByName")
public User getUserByName(@RequestParam String username){
return userServiceImpl.findByUsername(username);
}
@GetMapping("/findAll")
@PreAuthorize("hasAnyAuthority('ADMIN')") //這一步很重要 擁有ADMIN權限的用戶才能訪問該請求
public List<User> findAll(){
return userServiceImpl.list();
}
}
6. 創(chuàng)建登入驗證、SecurityConfig、攔截器文章來源:http://www.zghlxwxcb.cn/news/detail-518931.html
@Service
public class JwtUserServiceImpl implements UserDetailsService {
@Autowired
private IUserService userServiceImpl;
@Override
//根據(jù)用戶傳入的信息去數(shù)據(jù)庫查詢是否存在該用戶
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userServiceImpl.findByUsername(username);
if (user != null){
JwtUser jwtUser = new JwtUser(user);
return jwtUser;
}else {
try {
throw new UsernameNotFoundException("用戶不存在");
} catch (UsernameNotFoundException e) {
e.printStackTrace();
}
}
return null;
}
}
@EnableWebSecurity
// 只有加了@EnableGlobalMethodSecurity(prePostEnabled=true) 那么在上面使用的 @PreAuthorize(“hasAuthority(‘a(chǎn)dmin’)”)才會生效
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtUserServiceImpl jwtUserServiceImpl;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 這邊 通過重寫configure(),去數(shù)據(jù)庫查詢用戶是否存在
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserServiceImpl).passwordEncoder(bCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
// 以/user 開頭的請求 不需要進行驗證
// .antMatchers("/user/**").permitAll()
// 其他都放行了
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager())) // 用戶登錄攔截
.addFilter(new JWTAuthorizationFilter(authenticationManager())) // 權限攔截
// 不需要session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
/**
* 驗證用戶名密碼正確后,生成一個token,并將token返回給客戶端
* 該類繼承自UsernamePasswordAuthenticationFilter,重寫了其中的2個方法 ,
* attemptAuthentication:接收并解析用戶憑證。
* successfulAuthentication:用戶成功登錄后,這個方法會被調(diào)用,我們在這個方法里生成token并返回。
*/
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
/**
* security攔截默認是以POST形式走/login請求,我們這邊設置為走/token請求
* @param authenticationManager
*/
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
super.setFilterProcessesUrl("/login");
}
/**
* 接收并解析用戶憑證
* @param request
* @param response
* @return
* @throws AuthenticationException
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 從輸入流中獲取到登錄的信息
try {
User loginUser = new ObjectMapper().readValue(request.getInputStream(), User.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword())
);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
// 成功驗證后調(diào)用的方法
// 如果驗證成功,就生成token并返回
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
System.out.println("jwtUser:" + jwtUser.toString());
String role = "";
Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
for (GrantedAuthority authority : authorities) {
role = authority.getAuthority();
}
String token = JwtUtils.createToken(jwtUser.getUsername(), role);
// 返回創(chuàng)建成功的token 但是這里創(chuàng)建的token只是單純的token
// 按照jwt的規(guī)定,最后請求的時候應該是 `Bearer token`
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
String tokenStr = JwtUtils.TOKEN_PREFIX + token;
response.setHeader("token", tokenStr);
}
// 失敗 返回錯誤就行
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.getWriter().write("authentication failed, reason: " + failed.getMessage());
}
}
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@SneakyThrows
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String tokenHeader = request.getHeader(JwtUtils.TOKEN_HEADER);
// 如果請求頭中沒有Authorization信息則直接放行了
if (tokenHeader == null || !tokenHeader.startsWith(JwtUtils.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// 如果請求頭中有token,則進行解析,并且設置認證信息
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
super.doFilterInternal(request, response, chain);
}
// 這里從token中獲取用戶信息并新建一個token 就是上面說的設置認證信息
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws Exception {
String token = tokenHeader.replace(JwtUtils.TOKEN_PREFIX, "");
// 檢測token是否過期 如果過期會自動拋出錯誤
JwtUtils.isExpiration(token);
String username = JwtUtils.getUsername(token);
String role = JwtUtils.getUserRole(token);
if (username != null) {
return new UsernamePasswordAuthenticationToken(username, null,
Collections.singleton(new SimpleGrantedAuthority(role))
);
}
return null;
}
}
7. 啟動項目開始測試文章來源地址http://www.zghlxwxcb.cn/news/detail-518931.html
到了這里,關于SpringSecurity+Oauth2+JWT的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!