spring整合springSecurity

最近學習了黑馬的springSecurity,講的挺不錯的,兄弟我在這裏借鑑一下啦!豐富一下知識庫!

一、spring整合springSecurity第一版


1.1 導入依賴


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>

1.2 編寫Controller類


@Controller
public class TestController {
    @RequestMapping("hello")
    @ResponseBody
    public String hello(){
        return "success";
    }
}

1.3 編寫啓動類


@SpringBootApplication
public class SpringSecurityApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityApplication.class, args);
    }
}

1.4 訪問


http://localhost:8080/hello
在這裏插入圖片描述
跳轉到了springSecurity自帶的登錄頁面,這個應該是vue中的。


二、spring整合springSecurity第二版


2.1導入依賴


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

將包改成war包:

<packaging>war</packaging>

2.2加入jsp靜態頁面


在src\main創建創建webapp文件夾,並且將下圖的靜態資源導入到webapp:

在這裏插入圖片描述

修改 login.jsp中認證的url地址

在這裏插入圖片描述

修改 header.jsp中退出登錄的url地址

在這裏插入圖片描述


2.3編寫springSecurity配置類


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 這裏先不連接數據庫了
     */
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user")
                .password("{noop}123")
                .roles("USER");
    }
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login.jsp", "/failer.jsp", "/css/**", "/img/**",
                        "/plugins/**").permitAll()
                .antMatchers("/**").hasAnyRole("USER")
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .loginPage("/login.jsp")
                .loginProcessingUrl("/login")
                .successForwardUrl("/index.jsp")
                .failureForwardUrl("/failer.jsp")
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/logout")
                .invalidateHttpSession(true)
                .logoutSuccessUrl("/login.jsp")
                .permitAll()
                .and()
                .csrf()
                .disable();
    }
}

在controller中加入:

    @RequestMapping("/findAll")
    public String findAll(){
        return "product-list";
    }

2.4測試


在這裏插入圖片描述
在下圖配置spring-boot:run
在這裏插入圖片描述
訪問http://localhost:8080/findAll
跳轉到了自定義的頁面:
在這裏插入圖片描述
輸入配置類中的賬號密碼後,跳轉到了主頁面,現在訪問http://localhost:8080/findAll
頁面,就能跳轉過去了。
在這裏插入圖片描述


三、spring整合springSecurity第三版


3.1 導入依賴


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.1.5</version>
        </dependency>

3.2 application.xml文件配置


server:
  port: 8080
spring:
  mvc:
    view:
      prefix: /pages/
      suffix: .jsp
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///security_authority
    username: root
    password: root
mybatis:
  type-aliases-package: com.itheima.domain
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    com.itheima: debug

3.3 在啓動類上加DAO接口包掃描


@MapperScan("com.itheima.mapper")

3.4 創建pojo對象


public class SysRole implements GrantedAuthority {
	private Integer id;
	private String roleName;
	private String roleDesc;

	//標記此屬性不做json處理
	@JsonIgnore
	@Override
	public String getAuthority() {
	return roleName;
	}
}
public class SysUser implements UserDetails {
	private Integer id;
	private String username;
	private String password;
	private Integer status;
	private List<SysRole> roles = new ArrayList<>();
	
	@JsonIgnore
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
	return roles;
	}
	@Override
	public String getPassword() {
	return password;
	}
	@Override
	public String getUsername() {
	return username;
	}
	@JsonIgnore
	@Override
	public boolean isAccountNonExpired() {
	return true;
	}
	@JsonIgnore
	@Override
	public boolean isAccountNonLocked() {
	return true;
	}
	
	@JsonIgnore
	@Override
	public boolean isCredentialsNonExpired() {
	return true;
	}
	@JsonIgnore
	@Override
	public boolean isEnabled() {
	return true;
	}
}

3.5 創建mapper接口


public interface RoleMapper extends Mapper<SysRole> {
	@Select("SELECT r.id, r.role_name roleName, r.role_desc roleDesc " +
	"FROM sys_role r, sys_user_role ur " +
	"WHERE r.id=ur.rid AND ur.uid=#{uid}")
	public List<SysRole> findByUid(Integer uid);
}
public interface UserMapper extends Mapper<SysUser> {
	@Select("select * from sys_user where username=#{username}")
	@Results({
	@Result(id = true, property = "id", column = "id"),
	@Result(property = "roles", column = "id", javaType = List.class,
	many = @Many(select = "com.itheima.mapper.RoleMapper.findByUid"))
	})
	public SysUser findByUsername(String username);
}

3.6 創建service類


package com.itheima.service;
import org.springframework.security.core.userdetails.UserDetailsService;
public interface UserService extends UserDetailsService {
}

@Service
@Transactional
public class UserServiceImpl implements UserService {
	@Autowired
	private UserMapper userMapper;
	@Override
	public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
		return userMapper.findByUsername(s);
	}
}

3.7 在啓動類中將加密類注入IOC容器


@SpringBootApplication
@MapperScan("com.itheima.mapper")
public class SecurityApplication {
	public static void main(String[] args) {
		SpringApplication.run(SecurityApplication.class, args);
	}
	@Bean
	public BCryptPasswordEncoder passwordEncoder(){
		return new BCryptPasswordEncoder();
	}
}

3.8 修改配置類


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserService userService;
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
    }
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login.jsp", "/failer.jsp", "/css/**", "/img/**", "/plugins/**").permitAll()
                .antMatchers("/**").hasAnyRole("USER")
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .loginPage("/login.jsp")
                .loginProcessingUrl("/login")
                .successForwardUrl("/index.jsp")
                .failureForwardUrl("/failer.jsp")
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/logout")
                .invalidateHttpSession(true)
                .logoutSuccessUrl("/login.jsp")
                .permitAll()
                .and()
                .csrf()
                .disable();
    }
}
現在可以使用數據庫中的賬號密碼進行測試了。

3.9整合實現授權功能


在啓動類上添加開啓方法級的授權註解:
@EnableGlobalMethodSecurity(securedEnabled = true)

在方法上添加註解:

 @Secured("ROLE_ADMIN")

具有ROLE_ADMIN角色才能訪問。
在這裏插入圖片描述

3.10指定自定義攔截頁面


@ControllerAdvice
public class HandleControllerException {

	@ExceptionHandler(RuntimeException.class)
	public String exceptionHandler(RuntimeException e){
	if(e instanceof AccessDeniedException){
	//如果是權限不足異常,則跳轉到權限不足頁面!
		return "redirect:/403.jsp";
	}
	//其餘的異常都到500頁面!
	return "redirect:/500.jsp";
	}
}

3.11 SQL初始化腳本


/*
SQLyog Ultimate v12.08 (64 bit)
MySQL - 8.0.16 : Database - security_authority
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
/*Table structure for table `sys_permission` */

DROP TABLE IF EXISTS `sys_permission`;

CREATE TABLE `sys_permission` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '編號',
  `permission_NAME` varchar(30) DEFAULT NULL COMMENT '菜單名稱',
  `permission_url` varchar(100) DEFAULT NULL COMMENT '菜單地址',
  `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父菜單id',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `sys_permission` */

/*Table structure for table `sys_role` */

DROP TABLE IF EXISTS `sys_role`;

CREATE TABLE `sys_role` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '編號',
  `ROLE_NAME` varchar(30) DEFAULT NULL COMMENT '角色名稱',
  `ROLE_DESC` varchar(60) DEFAULT NULL COMMENT '角色描述',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

/*Data for the table `sys_role` */

/*Table structure for table `sys_role_permission` */

DROP TABLE IF EXISTS `sys_role_permission`;

CREATE TABLE `sys_role_permission` (
  `RID` int(11) NOT NULL COMMENT '角色編號',
  `PID` int(11) NOT NULL COMMENT '權限編號',
  PRIMARY KEY (`RID`,`PID`),
  KEY `FK_Reference_12` (`PID`),
  CONSTRAINT `FK_Reference_11` FOREIGN KEY (`RID`) REFERENCES `sys_role` (`ID`),
  CONSTRAINT `FK_Reference_12` FOREIGN KEY (`PID`) REFERENCES `sys_permission` (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `sys_role_permission` */

/*Table structure for table `sys_user` */

DROP TABLE IF EXISTS `sys_user`;

CREATE TABLE `sys_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) NOT NULL COMMENT '用戶名稱',
  `password` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密碼',
  `status` int(1) DEFAULT '1' COMMENT '1開啓0關閉',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

/*Data for the table `sys_user` */

/*Table structure for table `sys_user_role` */

DROP TABLE IF EXISTS `sys_user_role`;

CREATE TABLE `sys_user_role` (
  `UID` int(11) NOT NULL COMMENT '用戶編號',
  `RID` int(11) NOT NULL COMMENT '角色編號',
  PRIMARY KEY (`UID`,`RID`),
  KEY `FK_Reference_10` (`RID`),
  CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `sys_role` (`ID`),
  CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `sys_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `sys_user_role` */

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;


四、SpringSecurity 整合SpringBoot分佈式版


4.1分佈式認證概念說明

分佈式認證,即我們常說的單點登錄,簡稱SSO,指的是在多應用系統的項目中,用戶只需要登錄一次,就可以訪問所有互相信任的應用系統。

首先,我們要明確,在分佈式項目中,每臺服務器都有各自獨立的session,而這些session之間是無法直接共享資源的,所以,session通常不能被作爲單點登錄的技術方案。
在這裏插入圖片描述
總結一下,單點登錄的實現分兩大環節:
用戶認證:這一環節主要是用戶向認證服務器發起認證請求,認證服務器給用戶返回一個成功的令牌 token,
主要在認證服務器中完成,即圖中的A系統,注意A系統只能有一個。
身份校驗:這一環節是用戶攜帶 token去訪問其他服務器時,在其他服務器中要對token的真僞進行檢驗,主
要在資源服務器中完成,即圖中的B系統,這裏B系統可以有很多個。


4.2JWT 介紹


**概念說明** 從分佈式認證流程中,我們不難發現,這中間起最關鍵作用的就是token,token的安全與否,直接關係到系統的 健壯性,這裏我們選擇使用JWT來實現token的生成和校驗。JWT,全稱JSON Web Token,官網地址https://jwt.io ,是一款出色的分佈式身份校驗方案。可以生成token,也可以解析檢驗token。

JWT生成的token由三部分組成
頭部:主要設置一些規範信息,簽名部分的編碼格式就在頭部中聲明。
載荷: token中存放有效信息的部分,比如用戶名,用戶角色,過期時間等,但是不要放密碼,會泄露!
簽名:將頭部與載荷分別採用 base64編碼後,用“.”相連,再加入鹽,最後使用頭部聲明的編碼類型進行編
碼,就得到了簽名。

JWT生成token的安全性分析
從JWT生成的token組成上來看,要想避免token被僞造,主要就得看簽名部分了,而簽名部分又有三部分組成,其中頭部和載荷的base64編碼,幾乎是透明的,毫無安全性可言,那麼最終守護token安全的重擔就落在了加入的鹽上面了!

試想:如果生成token所用的鹽與解析token時加入的鹽是一樣的。豈不是類似於中國人民銀行把人民幣防僞技術
公開了?大家可以用這個鹽來解析token,就能用來僞造token。這時,我們就需要對鹽採用非對稱加密的方式進行加密,以達到生成token與校驗token方所用的鹽不一致的安全效果!


4.3非對稱加密 RSA介紹


基本原理:同時生成兩把密鑰:私鑰和公鑰,私鑰隱祕保存,公鑰可以下發給信任客戶端
私鑰加密,持有私鑰或公鑰纔可以解密
公鑰加密,持有私鑰纔可解密
優點:安全,難以破解
缺點:算法比較耗時,爲了安全,可以接受
歷史:三位數學家 Rivest、Shamir 和 Adleman 設計了一種算法,可以實現非對稱加密。這種算法用他們三
個人的名字縮寫:RSA。


4.4JWT相關工具類


        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.10.7</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>
public class JwtUtils {

    private static final String JWT_PAYLOAD_USER_KEY = "user";

    /**
     * 私鑰加密token
     *
     * @param userInfo   載荷中的數據
     * @param privateKey 私鑰
     * @param expire     過期時間,單位分鐘
     * @return JWT
     */
    public static String generateTokenExpireInMinutes(Object userInfo, PrivateKey privateKey, int expire) {
        return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
                .setId(createJTI())
                .setExpiration(DateTime.now().plusMinutes(expire).toDate())
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();
    }

    /**
     * 私鑰加密token
     *
     * @param userInfo   載荷中的數據
     * @param privateKey 私鑰
     * @param expire     過期時間,單位秒
     * @return JWT
     */
    public static String generateTokenExpireInSeconds(Object userInfo, PrivateKey privateKey, int expire) {
        return Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
                .setId(createJTI())
                .setExpiration(DateTime.now().plusSeconds(expire).toDate())
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();
    }

    /**
     * 公鑰解析token
     *
     * @param token     用戶請求中的token
     * @param publicKey 公鑰
     * @return Jws<Claims>
     */
    private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
        return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
    }

    private static String createJTI() {
        return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes()));
    }

    /**
     * 獲取token中的用戶信息
     *
     * @param token     用戶請求中的令牌
     * @param publicKey 公鑰
     * @return 用戶信息
     */
    public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey, Class<T> userType) {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setUserInfo(JsonUtils.toBean(body.get(JWT_PAYLOAD_USER_KEY).toString(), userType));
        claims.setExpiration(body.getExpiration());
        return claims;
    }

    /**
     * 獲取token中的載荷信息
     *
     * @param token     用戶請求中的令牌
     * @param publicKey 公鑰
     * @return 用戶信息
     */
    public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey) {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        claims.setExpiration(body.getExpiration());
        return claims;
    }
}
@Data
public class Payload<T> {
	private String id;
	private T userInfo;
	private Date expiration;
}

4.5 RSA工具類


public class RsaUtils {

    private static final int DEFAULT_KEY_SIZE = 2048;
    /**
     * 從文件中讀取公鑰
     *
     * @param filename 公鑰保存路徑,相對於classpath
     * @return 公鑰對象
     * @throws Exception
     */
    public static PublicKey getPublicKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPublicKey(bytes);
    }

    /**
     * 從文件中讀取密鑰
     *
     * @param filename 私鑰保存路徑,相對於classpath
     * @return 私鑰對象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPrivateKey(bytes);
    }

    /**
     * 獲取公鑰
     *
     * @param bytes 公鑰的字節形式
     * @return
     * @throws Exception
     */
    private static PublicKey getPublicKey(byte[] bytes) throws Exception {
        bytes = Base64.getDecoder().decode(bytes);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(spec);
    }

    /**
     * 獲取密鑰
     *
     * @param bytes 私鑰的字節形式
     * @return
     * @throws Exception
     */
    private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
        bytes = Base64.getDecoder().decode(bytes);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * 根據密文,生存rsa公鑰和私鑰,並寫入指定文件
     *
     * @param publicKeyFilename  公鑰文件路徑
     * @param privateKeyFilename 私鑰文件路徑
     * @param secret             生成密鑰的密文
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 獲取公鑰並寫出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
        writeFile(publicKeyFilename, publicKeyBytes);
        // 獲取私鑰並寫出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    private static byte[] readFile(String fileName) throws Exception {
        return Files.readAllBytes(new File(fileName).toPath());
    }

    private static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File(destPath);
        if (!dest.exists()) {
            dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }
}

4.6 SpringSecurity+JWT+RSA 分佈式認證思路分析


SpringSecurity主要是通過過濾器來實現功能的!我們要找到SpringSecurity實現認證和校驗身份的過濾器!

1、回顧集中式認證流程
用戶認證
使用UsernamePasswordAuthenticationFilter過濾器中attemptAuthentication方法實現認證功能,該過濾
器父類中successfulAuthentication方法實現認證成功後的操作。
身份校驗
使用BasicAuthenticationFilter過濾器中doFilterInternal方法驗證是否登錄,以決定能否進入後續過濾器。

2、分析分佈式認證流程
用戶認證
由於,分佈式項目,多數是前後端分離的架構設計,我們要滿足可以接受異步post的認證請求參數,需要修
改UsernamePasswordAuthenticationFilter過濾器中attemptAuthentication方法,讓其能夠接收請求體。另外,默認 successfulAuthentication方法在認證通過後,是把用戶信息直接放入session就完事了,現在我們需要修改這個方法,在認證通過後生成token並返回給用戶。
身份校驗
原來BasicAuthenticationFilter過濾器中doFilterInternal方法校驗用戶是否登錄,就是看session中是否有用
戶信息,我們要修改爲,驗證用戶攜帶的token是否合法,並解析出用戶信息,交給SpringSecurity,以便於
後續的授權功能可以正常使用。


4.7 SpringSecurity+JWT+RSA 分佈式認證實現


1、創建父工程`springboot_security_jwt_rsa_parent`
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/>
    </parent>

2、創建通用子模塊security_jwt_rsa_common

    <dependencies>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.10.7</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.10.7</version>
            <scope>runtime</scope>
        </dependency>
        <!--jackson包-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.9</version>
        </dependency>
        <!--日誌包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

3、導入工具類
在這裏插入圖片描述
4、在通用子模塊中編寫測試類生成 rsa公鑰和私鑰

public class RsaUtilsTest {
	private String publicFile = "D:\\auth_key\\rsa_key.pub";
	private String privateFile = "D:\\auth_key\\rsa_key";
	@Test
	public void generateKey() throws Exception {
		RsaUtils.generateKey(publicFile, privateFile, "heima", 2048);
	}
}

5、創建子模塊認證服務工程security_jwt_rsa_auth_server

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>com.itheima</groupId>
            <artifactId>heima_common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
    </dependencies>
server:
  port: 9001
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///security_authority
    username: root
    password: root
mybatis:
  type-aliases-package: com.itheima.domain
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    com.itheima: debug
rsa:
  key:
    pubKeyFile: D:\auth_key\id_key_rsa.pub
    priKeyFile: D:\auth_key\id_key_rsa
    

4.8 提供解析公鑰和私鑰的配置類


import com.itheima.utils.RsaUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;

import javax.annotation.PostConstruct;
import java.security.PrivateKey;
import java.security.PublicKey;

@ConfigurationProperties("rsa.key")
public class RsaKeyProperties {

    private String pubKeyFile;
    private String priKeyFile;

    private PublicKey publicKey;
    private PrivateKey privateKey;

    @PostConstruct
    public void createRsaKey() throws Exception {
        publicKey = RsaUtils.getPublicKey(pubKeyFile);
        privateKey = RsaUtils.getPrivateKey(priKeyFile);
    }

    public String getPubKeyFile() {
        return pubKeyFile;
    }

    public void setPubKeyFile(String pubKeyFile) {
        this.pubKeyFile = pubKeyFile;
    }

    public String getPriKeyFile() {
        return priKeyFile;
    }

    public void setPriKeyFile(String priKeyFile) {
        this.priKeyFile = priKeyFile;
    }

    public PublicKey getPublicKey() {
        return publicKey;
    }

    public void setPublicKey(PublicKey publicKey) {
        this.publicKey = publicKey;
    }

    public PrivateKey getPrivateKey() {
        return privateKey;
    }

    public void setPrivateKey(PrivateKey privateKey) {
        this.privateKey = privateKey;
    }
}

4.9 創建認證服務啓動類


@SpringBootApplication
@MapperScan("com.itheima.mapper")
@EnableConfigurationProperties(RsaKeyProperties.class)
public class AuthServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthServerApplication.class, args);
    }
}


4.10 將上面集中式案例中數據庫認證相關代碼複製到認證服務中


在這裏插入圖片描述


4.11 編寫檢驗token過濾器


import com.fasterxml.jackson.databind.ObjectMapper;
import com.itheima.config.RsaKeyProperties;
import com.itheima.domain.Payload;
import com.itheima.domain.SysUser;
import com.itheima.utils.JwtUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

public class JwtVerifyFilter extends BasicAuthenticationFilter {

    private RsaKeyProperties prop;

    public JwtVerifyFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop) {
        super(authenticationManager);
        this.prop = prop;
    }

    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Bearer ")) {
            //如果攜帶錯誤的token,則給用戶提示請登錄!
            chain.doFilter(request, response);
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            PrintWriter out = response.getWriter();
            Map resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_FORBIDDEN);
            resultMap.put("msg", "請登錄!");
            out.write(new ObjectMapper().writeValueAsString(resultMap));
            out.flush();
            out.close();
        } else {
            //如果攜帶了正確格式的token要先得到token
            String token = header.replace("Bearer ", "");
            //驗證tken是否正確
            Payload<SysUser> payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), SysUser.class);
            SysUser user = payload.getUserInfo();
            if(user!=null){
                UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authResult);
                chain.doFilter(request, response);
            }
        }
    }

}

4.12 編寫認證過濾器


import com.fasterxml.jackson.databind.ObjectMapper;
import com.itheima.config.RsaKeyProperties;
import com.itheima.domain.SysRole;
import com.itheima.domain.SysUser;
import com.itheima.utils.JwtUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;
    private RsaKeyProperties prop;

    public JwtLoginFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop) {
        this.authenticationManager = authenticationManager;
        this.prop = prop;
    }

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            SysUser sysUser = new ObjectMapper().readValue(request.getInputStream(), SysUser.class);
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
            return authenticationManager.authenticate(authRequest);
        }catch (Exception e){
            try {
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                PrintWriter out = response.getWriter();
                Map resultMap = new HashMap();
                resultMap.put("code", HttpServletResponse.SC_UNAUTHORIZED);
                resultMap.put("msg", "用戶名或密碼錯誤!");
                out.write(new ObjectMapper().writeValueAsString(resultMap));
                out.flush();
                out.close();
            }catch (Exception outEx){
                outEx.printStackTrace();
            }
            throw new RuntimeException(e);
        }
    }

    public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        SysUser user = new SysUser();
        user.setUsername(authResult.getName());
        user.setRoles((List<SysRole>) authResult.getAuthorities());
        String token = JwtUtils.generateTokenExpireInMinutes(user, prop.getPrivateKey(), 24 * 60);
        response.addHeader("Authorization", "Bearer "+token);
        try {
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_OK);
            PrintWriter out = response.getWriter();
            Map resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_OK);
            resultMap.put("msg", "認證通過!");
            out.write(new ObjectMapper().writeValueAsString(resultMap));
            out.flush();
            out.close();
        }catch (Exception outEx){
            outEx.printStackTrace();
        }
    }

}

4.13 編寫SpringSecurity配置類


import com.itheima.filter.JwtLoginFilter;
import com.itheima.filter.JwtVerifyFilter;
import com.itheima.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Autowired
    private RsaKeyProperties prop;

    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //指定認證對象的來源
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }
    //SpringSecurity配置信息
    public void configure(HttpSecurity http) throws Exception {
        http.csrf()
            .disable()
            .authorizeRequests()
            .antMatchers("/product").hasAnyRole("USER")
            .anyRequest()
            .authenticated()
            .and()
            .addFilter(new JwtLoginFilter(super.authenticationManager(), prop))
            .addFilter(new JwtVerifyFilter(super.authenticationManager(), prop))
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

4.14 驗證


在這裏插入圖片描述
認證通過之後:
在這裏插入圖片描述
在這裏插入圖片描述
驗證認證請求:
在這裏插入圖片描述


4.15 創建子模塊資源服務security_jwt_rsa_source_product


   <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>com.itheima</groupId>
            <artifactId>heima_common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
    </dependencies>
server:
  port: 9002
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///security_authority
    username: root
    password: root
mybatis:
  type-aliases-package: com.itheima.domain
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    com.itheima: debug
rsa:
  key:
    pubKeyFile: D:\auth_key\id_key_rsa.pub

4.16 編寫讀取公鑰的配置類


@ConfigurationProperties("rsa.key")
public class RsaKeyProperties {

    private String pubKeyFile;

    private PublicKey publicKey;

    @PostConstruct
    public void createRsaKey() throws Exception {
        publicKey = RsaUtils.getPublicKey(pubKeyFile);
    }

    public String getPubKeyFile() {
        return pubKeyFile;
    }

    public void setPubKeyFile(String pubKeyFile) {
        this.pubKeyFile = pubKeyFile;
    }

    public PublicKey getPublicKey() {
        return publicKey;
    }

    public void setPublicKey(PublicKey publicKey) {
        this.publicKey = publicKey;
    }

}

4.17 資源啓動類


@SpringBootApplication
@MapperScan("com.itheima.mapper")
@EnableConfigurationProperties(RsaKeyProperties.class)
public class AuthSourceApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthSourceApplication.class, args);
    }
}

4.18 複製認證服務中,用戶對象,角色對象和校驗認證的接口


在這裏插入圖片描述


4.19 複製認證服務中 SpringSecurity配置類做修改


去掉“增加自定義認證過濾器”即可!
public class JwtVerifyFilter extends BasicAuthenticationFilter {

    private RsaKeyProperties prop;

    public JwtVerifyFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop) {
        super(authenticationManager);
        this.prop = prop;
    }

    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Bearer ")) {
            //如果攜帶錯誤的token,則給用戶提示請登錄!
            chain.doFilter(request, response);
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            PrintWriter out = response.getWriter();
            Map resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_FORBIDDEN);
            resultMap.put("msg", "請登錄!");
            out.write(new ObjectMapper().writeValueAsString(resultMap));
            out.flush();
            out.close();
        } else {
            //如果攜帶了正確格式的token要先得到token
            String token = header.replace("Bearer ", "");
            //驗證tken是否正確
            Payload<SysUser> payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), SysUser.class);
            SysUser user = payload.getUserInfo();
            if(user!=null){
                UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authResult);
                chain.doFilter(request, response);
            }
        }
    }


}

4.20 編寫控制器


@RestController
@RequestMapping("/product")
public class ProductController {

    @RequestMapping("/findAll")
    public String findAll(){
        return "產品列表查詢成功!";
    }

}

5.21 資源服務測試


在這裏插入圖片描述
在產品處理器上添加訪問需要 ADMIN角色

@Secured("ROLE_PRODUCT")

重啓測試權限不足:
在這裏插入圖片描述
在數據庫中手動給用戶添加 ADMIN角色
重新認證獲取新 token再測試OK了!

這是黑馬教學文檔中摘取的,文檔寫的特別棒,講的也非常好!http://www.itheima.com/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章