springboot中使用Spring Security 之微服務的使用(六)


一、什么是微服務

  微服務由來
微服務最早由 Martin Fowler 與 James Lewis 於 2014 年共同提出,微服務架構風格是一種
使用一套小服務來開發單個應用的方式途徑,每個服務運行在自己的進程中,並使用輕量
級機制通信,通常是 HTTP API,這些服務基於業務能力構建,並能夠通過自動化部署機制
來獨立部署,這些服務使用不同的編程語言實現,以及不同數據存儲技術,並保持最低限
度的集中式管理。
  微服務優勢
(1)微服務每個模塊就相當於一個單獨的項目,代碼量明顯減少,遇到問題也相對來說比
較好解決。
(2)微服務每個模塊都可以使用不同的存儲方式(比如有的用 redis,有的用 mysql
等),數據庫也是單個模塊對應自己的數據庫。
(3)微服務每個模塊都可以使用不同的開發技術,開發模式更靈活。
  微服務本質
(1)微服務,關鍵其實不僅僅是微服務本身,而是系統要提供一套基礎的架構,這種架構
使得微服務可以獨立的部署、運行、升級,不僅如此,這個系統架構還讓微服務與微服務
之間在結構上“松耦合”,而在功能上則表現為一個統一的整體。這種所謂的“統一的整
體”表現出來的是統一風格的界面,統一的權限管理,統一的安全策略,統一的上線過
程,統一的日志和審計方法,統一的調度方式,統一的訪問入口等等。
(2)微服務的目的是有效的拆分應用,實現敏捷開發和部署。
   微服務認證與授權實現思路
1、認證授權過程分析
(1)如果是基於 Session,那么 Spring-security 會對 cookie 里的 sessionid 進行解析,找
到服務器存儲的 session 信息,然后判斷當前用戶是否符合請求的要求。
(2)如果是 token,則是解析出 token,然后將當前請求加入到 Spring-security 管理的權限
信息中去
 

 

 

如果系統的模塊眾多,每個模塊都需要進行授權與認證,所以我們選擇基於 token 的形式
進行授權與認證,用戶根據用戶名密碼認證成功,然后獲取當前用戶角色的一系列權限
值,並以用戶名為 key,權限列表為 value 的形式存入 redis 緩存中,根據用戶名相關信息
生成 token 返回,瀏覽器將 token 記錄到 cookie 中,每次調用 api 接口都默認將 token 攜帶
到 header 請求頭中,Spring-security 解析 header 頭獲取 token 信息,解析 token 獲取當前
用戶名,根據用戶名就可以從 redis 中獲取權限列表,這樣 Spring-security 就能夠判斷當前
請求是否有權限訪問
 

二、權限管理數據模型

數據模型介紹

*添加角色
*為角色分配菜單

 

*添加用戶

*為用戶分配菜單

 

一共5張表:

security.sql(微服務登錄中有)

拿這修改就行

 

 

 微服務權限管理案例主要功能:

使用技術說明

1、登錄(認證)

2、添加角色

3、為角色分配菜單

4、添加用戶

5、為用戶分配角色

 

1、Maven

創建父工程:管理項目依賴版本

創建子模塊:使用具體依賴

2.SpringBoot 本質就是Spring

3.MyBatisPlus 操作數據庫框架

4.SpringCloud

(1)GateWay 網關

(2)注冊中心 Nacos

其他技術:

Redis  Jwt  Swagger

 

搭建項目工程

1、創建父工程 acl_parent  :管理依賴版本

2、在父工程創建子模塊

  (1)common

    *service_base:工具類

    *spring_security :權限配置

  (2)infrastructure

    *api_gateway: 網關

  (3)service

    *service_acl:權限管理微服務模塊

 

 

 接下來:引入依賴(微服務登錄中有)

 引入完依賴啟動redis和Nacos

Nacos介紹

 

 

 

 

上面是Windows下面是Linux

 訪問地址:http://localhost:8848/nacos/

默認用戶名密碼:nacos

工具類

編寫common工具類

  1、編寫common里面需要的工具類

 

 

  

 2.編寫security工具類

 

 

 

 

 一.先寫密碼處理工具類

 

 

@Component
public class DefaultPasswordEncoder implements PasswordEncoder {

public DefaultPasswordEncoder(){
this(-1);
}

public DefaultPasswordEncoder(int strength){
}

//進行MD5加密
@Override
public String encode(CharSequence charSequence) {
return MD5.encrypt(charSequence.toString());
}

//進行密碼對比
@Override
public boolean matches(CharSequence charSequence, String encodedPassword) {
return encodedPassword.equals(MD5.encrypt(charSequence.toString()));
}
}
二.token操作工具類
使用jwt生成token
//1.使用jwt根據用戶名生成token
@Component
public class TokenManager {

//token有效時長
private long tokenEcpriation=24*60*60*1000;
//編碼秘鑰
private String tokenSignKey ="123456";
//1.使用jwt根據用戶名生成token
public String createToken(String username){
String token = Jwts.builder().setSubject(username)
.setExpiration(new Date(System.currentTimeMillis()+tokenEcpriation))
.signWith(SignatureAlgorithm.HS512 ,tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
return token;
}
//2.根據token字符串得到用戶信息
public String getUserInfoFromToken(String token){
String userinfo = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
return userinfo;
}

//3刪除token
public void removeToken(String token){
}
}

三.退出處理器
//退出處理器
public class TokenLogoutHandler implements LogoutHandler {

private TokenManager tokenManager;
private RedisTemplate redisTemplate;

public TokenLogoutHandler(TokenManager tokenManager,RedisTemplate redisTemplate){
this.tokenManager =tokenManager;
this.redisTemplate=redisTemplate;
}

@Override
public void logout(HttpServletRequest request,HttpServletResponse httpServletResponse, Authentication authentication) {
//1.從header里面獲取token

//2.token不為空,移除token,從redis刪除token
String token = request.getHeader("token");
if (token!=null){
//移除
tokenManager.removeToken(token);

//從token獲取用戶名
String username = tokenManager.getUserInfoFromToken(token);
redisTemplate.delete(username);
}

}
}

未授權統一處理類
public class UnauthEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
ResponseUtil.out(httpServletResponse, R.error());
}
}



編寫security認證過濾器

 

 准備兩個實體類

User

@Data
@ApiModel(description = "用戶實體類")
public class User implements Serializable {
private String username;
private String password;
private String nickName;
private String salt;
private String token;
}

SecurityUser類
@Data
@Slf4j
public class SecurityUser implements UserDetails {
//當前登錄用戶
private transient User currentUserInfo;
//當前權限
private List<String> permissionValueList;
public SecurityUser() {
}
public SecurityUser(User user) {
if (user != null) {
this.currentUserInfo = user;
}
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for(String permissionValue : permissionValueList) {
if(StringUtils.isEmpty(permissionValue)) continue;
SimpleGrantedAuthority authority = new
SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
return authorities;
}
@Override
public String getPassword() {
return currentUserInfo.getPassword();
}
@Override
public String getUsername() {
return currentUserInfo.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

在編寫security認證過濾

1.認證過濾器:

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    private TokenManager tokenManager;
private RedisTemplate redisTemplate;
private AuthenticationManager authenticationManager;

public TokenLoginFilter(AuthenticationManager authenticationManager,TokenManager tokenManager,RedisTemplate redisTemplate){
this.authenticationManager=authenticationManager;
this.tokenManager=tokenManager;
this.redisTemplate=redisTemplate;
this.setPostOnly(false);
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
}

//1.獲取表單提交用戶名和密碼
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException{
//獲取表單提交數據
try {
User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),
new ArrayList<>()));
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException();
}
}

//2.認證成功的方法
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
//認證成功,得到認證成功之后的信息
SecurityUser user = (SecurityUser) authResult.getPrincipal();
//根據用戶名生成token
String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
//把用戶名稱和用戶權限列表放到redis
redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());
//返回token
ResponseUtil.out(response, R.ok().data("token",token));
}

//3.認證失敗調用的方法
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
ResponseUtil.out(response, R.error());
}
}

2.授權過濾器
 public class TokenAuthFilter extends BasicAuthenticationFilter {
    private TokenManager tokenManager;
private RedisTemplate redisTemplate;

public TokenAuthFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
this.tokenManager= tokenManager;
this.redisTemplate=redisTemplate;
}

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//獲取當前認證成功用戶權限信息
UsernamePasswordAuthenticationToken authRequest= getAuthentication(request);
//判斷如果有權限信息,放到權限上下文中
if (authRequest!=null){
SecurityContextHolder.getContext().setAuthentication(authRequest);
}
chain.doFilter(request,response);
}

private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request){
//1.從header獲取token
String token = request.getHeader("token");
if (token!=null){
//從token獲取用戶名
String username = tokenManager.getUserInfoFromToken(token);

//從redis獲取對應權限列表
List<String>permissionValueList = (List<String>) redisTemplate.opsForValue().get(username);
//創建一個Collection<GrantedAuthority>集合
Collection<GrantedAuthority> authority=new ArrayList<>();
//遍歷
for (String permissionValue : permissionValueList) {
SimpleGrantedAuthority auth=new SimpleGrantedAuthority(permissionValue);
authority.add(auth);
}
return new UsernamePasswordAuthenticationToken(username,token,authority);
}
return null;
}
}

編寫核心配置類
  

@Configuration
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
private DefaultPasswordEncoder defaultPasswordEncoder;
private UserDetailsService userDetailsService;

@Autowired
public TokenWebSecurityConfig(UserDetailsService userDetailsService,DefaultPasswordEncoder defaultPasswordEncoder,
TokenManager tokenManager,RedisTemplate redisTemplate ){
this.userDetailsService=userDetailsService;
this.defaultPasswordEncoder=defaultPasswordEncoder;
this.tokenManager=tokenManager;
this.redisTemplate=redisTemplate;
}

/**
* 配置設置
*/
//設置退出的地址和 token,redis 操作地址
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new UnauthEntryPoint())//沒有權限訪問
.and().csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().logout().logoutUrl("/admin/acl/index/logout")//退出路徑
.addLogoutHandler(new TokenLogoutHandler
(tokenManager,redisTemplate)).and()
.addFilter(new TokenLoginFilter
(authenticationManager(),tokenManager, redisTemplate))
.addFilter(new TokenAuthFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
}
/**
* 調用userDetailsService和密碼處理
* */

@Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
}
/**
* * 配置哪些請求不攔截
* 不進行認證,可以直接訪問
* */
@Override
public void configure(WebSecurity web)
throws Exception {
web.ignoring().antMatchers("/api/**", "/swagger-ui.html/**");
}
}

編寫UserDetailsService
  如圖:
                                      

 

 

代碼做參考

                                                      

 

 

整合權限管理模塊
  application.properties
  
#服務端口
server.port=8009
#服務器
spring.application.name=service-acl
#mysql數據庫連接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3307/acldb?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root

#redis連接
spring.redis.host=192.168.24.144
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待時間(負數表示沒有限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
#最小空閑


#配置mapper xml文件的路徑
mybatis-plus.mapper-locations=classpath:com/atguigu/aclservice/mapper/xml/*.xml
#nacos服務地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#返回json的全局時間格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
 
            
           

 前端頁面:

                                    

 前端啟動命令: npm run dev

 前端啟動報如下錯誤:

  

使用npm run dev 運行,出現如下錯誤 :

Failed to compile.

  ./src/styles/index.scss (./node_modules/css-loader??ref--11-1!./node_modules/postcss-loader/lib??ref--11-2!./node_modules/sass-loader/lib/loader.js??ref--11-3!./src/styles/index.scss)

Module build failed (from ./node_modules/sass-loader/lib/loader.js):

Error: Missing binding E:\test\node_modules\node-sass\vendor\win32-x64-67\binding.node

Node Sass could not find a binding for your current environment: Windows 64-bit with Node.js 11.x

 

Found bindings for the following environments:

  - Windows 64-bit with Node.js 10.x

 

This usually happens because your environment has changed since running `npm install`.

Run `npm rebuild node-sass` to download the binding for your current environment.

 

首先在項目目錄下依次安裝以下文件:

npm install node-sass 

npm i node-sass -D

如果繼續報錯,會提醒安裝element-ui就可以運行項目了,我們繼續在項目目錄下安裝它就好了:

npm install --save element-ui

然后運行項目就OK啦,終於不報錯啦!

 

后端錯誤:java.lang.NullPointerException: null

解決方法:

  

 

 重新啟動就可以登錄了

            

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM