<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> </dependencies>
package com.weiwei.xu.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping(value = "/product") public class ProductController { @RequestMapping(value = "/findAll") @ResponseBody public String findAll(){ return "success"; } }
package com.weiwei.xu; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringBootSecurityApplication { public static void main(String[] args) { SpringApplication.run(SpringBootSecurityApplication.class, args); } }
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
<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>
package com.weiwei.xu.config; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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; /** * SpringSecurity的配置类 * * @author weiwei.xu */ @Configuration @EnableWebSecurity public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { /** * 认证用户的来源[内存或数据库] * * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("user").password("{noop}123456").roles("USER"); } /** * SpringSecurity相关信息 * * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { //释放静态资源,指定资源拦截规则,指定自定义认证页面,指定退出认证配置,csrf配置 http.authorizeRequests().antMatchers("/login.jsp","/failer.jsp","/css/**","/img/**","/pages/**").permitAll() .antMatchers("/**").access("hasAnyRole('USER','ADMIN')") .anyRequest() .authenticated() .and() .formLogin().loginPage("/login.jsp").loginProcessingUrl("/login").successForwardUrl("/index.jsp").failureForwardUrl("/failer.jsp").permitAll() .and() .logout().logoutUrl("/logout").logoutSuccessUrl("/login.jsp") .invalidateHttpSession(true) //清空session .permitAll(); } }
package com.weiwei.xu; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; /** * 启动类 * * @author weiwei.xu */ @SpringBootApplication(exclude={DataSourceAutoConfiguration.class}) public class SpringBootSecurityApplication extends SpringBootServletInitializer { public static void main(String[] args) { SpringApplication.run(SpringBootSecurityApplication.class, args); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(SpringBootSecurityApplication.class); } }
server: port: 8080 # 配置视图解析器 spring: mvc: view: prefix: /pages/ suffix: .jsp
/* Navicat Premium Data Transfer Source Server : 192.168.1.57 Source Server Type : MySQL Source Server Version : 50729 Source Host : 192.168.1.57:33060 Source Schema : security Target Server Type : MySQL Target Server Version : 50729 File Encoding : 65001 Date: 26/04/2020 11:18:36 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for persistent_logins -- ---------------------------- DROP TABLE IF EXISTS `persistent_logins`; CREATE TABLE `persistent_logins` ( `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `series` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `token` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `last_used` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0), PRIMARY KEY (`series`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of persistent_logins -- ---------------------------- INSERT INTO `persistent_logins` VALUES ('xiaoming', '93OEeplI7zAKwc0BqVnW+Q==', 'e4JvptA5h8fR+FFgmJSL9g==', '2020-04-24 10:23:57'); INSERT INTO `persistent_logins` VALUES ('xiaoming', 'CmWGQ/rmb7lXAx7dHiEVGw==', 'OvsaKtaC046whNL56Y4MqA==', '2020-04-24 10:30:29'); INSERT INTO `persistent_logins` VALUES ('xiaoming', 'JJqJ5vJ/7IqMLxj/JpLDRg==', 'vIfFHeD6fYSV8oCHMdty7g==', '2020-04-24 10:42:48'); INSERT INTO `persistent_logins` VALUES ('xiaoming', 'T9NMx6AiG5J3RNSM841juw==', 'v67JBqXg7AkhLtH++LISNw==', '2020-04-24 10:35:57'); -- ---------------------------- -- Table structure for sys_permission -- ---------------------------- DROP TABLE IF EXISTS `sys_permission`; CREATE TABLE `sys_permission` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号', `permission_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单名称', `permission_url` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单地址', `parent_id` int(11) NOT NULL DEFAULT 0 COMMENT '父菜单id', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for sys_role -- ---------------------------- DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号', `role_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称', `role_desc` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_role -- ---------------------------- INSERT INTO `sys_role` VALUES (6, 'ROLE_USER', '普通用户'); INSERT INTO `sys_role` VALUES (7, 'ROLE_ADMIN', '系统管理员'); -- ---------------------------- -- Table structure for 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`) USING BTREE, INDEX `FK_Reference_12`(`pid`) USING BTREE, CONSTRAINT `FK_Reference_11` FOREIGN KEY (`rid`) REFERENCES `sys_role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT `FK_Reference_12` FOREIGN KEY (`pid`) REFERENCES `sys_permission` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for sys_user -- ---------------------------- DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名称', `password` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码', `status` int(1) NULL DEFAULT 1 COMMENT '1开启0关闭', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_user -- ---------------------------- INSERT INTO `sys_user` VALUES (4, 'admin', '$2a$10$ynlaufZM048G5jsp98seeuvkAXNCVD5RFEudlrW.xiNihU.2Tjm9W', 1); INSERT INTO `sys_user` VALUES (5, 'xiaoming', '$2a$10$ynlaufZM048G5jsp98seeuvkAXNCVD5RFEudlrW.xiNihU.2Tjm9W', 1); -- ---------------------------- -- Table structure for 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`) USING BTREE, INDEX `FK_Reference_10`(`rid`) USING BTREE, CONSTRAINT `FK_Reference_10` FOREIGN KEY (`rid`) REFERENCES `sys_role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT `FK_Reference_9` FOREIGN KEY (`uid`) REFERENCES `sys_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_user_role -- ---------------------------- INSERT INTO `sys_user_role` VALUES (5, 6); INSERT INTO `sys_user_role` VALUES (4, 7); SET FOREIGN_KEY_CHECKS = 1;
<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.2</version> </dependency>
server: port: 8080 # 配置视图解析器 spring: mvc: view: prefix: /pages/ suffix: .jsp datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.1.57:33060/security username: root password: 123456 mybatis: type-aliases-package: com.weiwei.xu.domain mapper-locations: classpath:/com/weiwei/xu/mapper/*.mapper.xml
package com.weiwei.xu.domain; import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.HashSet; import java.util.Set; /** * 系统用户 * * @author weiwei.xu */ public class SysUser implements UserDetails { private Integer id; private String username; private String password; private Integer status; private Set<SysRole> roles = new HashSet<>(); @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } @Override public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public Set<SysRole> getRoles() { return roles; } public void setRoles(Set<SysRole> roles) { this.roles = roles; } @JsonIgnore @Override public Collection<? extends GrantedAuthority> getAuthorities() { return roles; } }
package com.weiwei.xu.domain; import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.security.core.GrantedAuthority; /** * 系统角色 * * @author weiwei.xu */ public class SysRole implements GrantedAuthority { private Integer id; private String roleName; private String roleDesc; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public String getRoleDesc() { return roleDesc; } public void setRoleDesc(String roleDesc) { this.roleDesc = roleDesc; } @JsonIgnore @Override public String getAuthority() { return roleName; } }
package com.weiwei.xu.mapper; import com.weiwei.xu.domain.SysUser; import org.apache.ibatis.annotations.Many; import org.apache.ibatis.annotations.Result; import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Select; import java.util.Set; public interface SysUserMapper { @Select("select * from sys_user where username = #{username} ") @Results({ @Result(id = true, property = "id", column = "id"), @Result(property = "roles", column = "id", javaType = Set.class, many = @Many(select = "com.weiwei.xu.mapper.SysRoleMapper.findByUid")) }) SysUser findByUsername(String username); }
package com.weiwei.xu.mapper; import com.weiwei.xu.domain.SysRole; import org.apache.ibatis.annotations.Select; import java.util.Set; public interface SysRoleMapper { /** * 根据uid查询用户所对应的角色 * * @param uid * @return */ @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}") Set<SysRole> findByUid(Integer uid); }
package com.weiwei.xu.service; import org.springframework.security.core.userdetails.UserDetailsService; public interface SysUserService extends UserDetailsService { }
package com.weiwei.xu.service.impl; import com.weiwei.xu.mapper.SysUserMapper; import com.weiwei.xu.service.SysUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @Transactional public class SysUserServiceImpl implements SysUserService { @Autowired private SysUserMapper sysUserMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return sysUserMapper.findByUsername(username); } }
package com.weiwei.xu.config; import com.weiwei.xu.service.SysUserService; 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.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.crypto.bcrypt.BCryptPasswordEncoder; /** * SpringSecurity的配置类 * * @author weiwei.xu */ @Configuration @EnableWebSecurity public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private SysUserService sysUserService; @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 认证用户的来源[内存或数据库] * * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(sysUserService).passwordEncoder(passwordEncoder()); } /** * SpringSecurity相关信息 * * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { //释放静态资源,指定资源拦截规则,指定自定义认证页面,指定退出认证配置,csrf配置 http.authorizeRequests().antMatchers("/login.jsp", "/failer.jsp", "/css/**", "/img/**", "/pages/**").permitAll() .antMatchers("/**").access("hasAnyRole('USER','ADMIN')") .anyRequest() .authenticated() .and() .formLogin().loginPage("/login.jsp").loginProcessingUrl("/login").successForwardUrl("/index.jsp").failureForwardUrl("/failer.jsp").permitAll() .and() .logout().logoutUrl("/logout").logoutSuccessUrl("/login.jsp") .invalidateHttpSession(true) //清空session .permitAll(); } }
package com.weiwei.xu; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; /** * 启动类 * * @author weiwei.xu */ @MapperScan("com.weiwei.xu.mapper") @SpringBootApplication @EnableGlobalMethodSecurity(securedEnabled = true) public class SpringBootSecurityApplication extends SpringBootServletInitializer { public static void main(String[] args) { SpringApplication.run(SpringBootSecurityApplication.class, args); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(SpringBootSecurityApplication.class); } }
package com.weiwei.xu.controller; import org.springframework.security.access.annotation.Secured; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping(value = "/product") public class ProductController { @RequestMapping(value = "/findAll") @Secured({"ROLE_PRODUCT","ROLE_ADMIN"}) public String findAll(){ return "product-list"; } }
package com.weiwei.xu.exception; import org.springframework.security.acls.model.NotFoundException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import java.nio.file.AccessDeniedException; @ControllerAdvice public class SecurityExceptionHandler { @ExceptionHandler(value = AccessDeniedException.class) public String handle403Exception(){ return "redirect:403"; } @ExceptionHandler(value = RuntimeException.class) public String handle500Exception(){ return "redirect:500"; } @ExceptionHandler(value = NotFoundException.class) public String handle404Exception(){ return "redirect:404"; } }
总结,单点登录要实现的两大环节:
JWT生成的token由以下三部分组成:
基本原理:同时生成两把密钥:私钥和公钥,私钥隐藏保存,公钥可以发给信任客户端。
SpringSecurity主要是通过过滤器来实现功能的,所以我们需要找到SpringSecurity实现认证和校验身份的过滤器。
用户认证:
身份校验:
用户认证:
身份认证:
<properties> <java.version>1.8</java.version> <!-- swagger --> <io.springfox.version>2.9.2</io.springfox.version> <io.swagger.version>1.5.21</io.swagger.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <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> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>4.6.4</version> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.2</version> </dependency> <!-- Swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${io.springfox.version}</version> <exclusions> <exclusion> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> </exclusion> <exclusion> <groupId>io.swagger</groupId> <artifactId>swagger-models</artifactId> </exclusion> <exclusion> <artifactId>mapstruct</artifactId> <groupId>org.mapstruct</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${io.springfox.version}</version> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>${io.swagger.version}</version> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-models</artifactId> <version>${io.swagger.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
package com.weiwei.xu.utils; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.security.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; /** * RSA算法 * * @author weiwei.xu */ 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.getParentFile().exists()) { dest.getParentFile().mkdirs(); } if (!dest.exists()) { dest.createNewFile(); } Files.write(dest.toPath(), bytes); } }
package com.weiwei.xu.utils; import cn.hutool.json.JSONUtil; import com.weiwei.xu.common.Payload; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.security.PrivateKey; import java.security.PublicKey; import java.sql.Date; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Base64; import java.util.UUID; /** * 生成token以及校验token相关方法 * * @author: weiwei.xu */ 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, JSONUtil.parseObj(userInfo).toStringPretty()) .setId(createJTI()) .setExpiration(Date.from(LocalDateTime.now().plusMinutes(expire).atZone(ZoneId.systemDefault()).toInstant())) .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, JSONUtil.parseObj(userInfo).toStringPretty()) .setId(createJTI()) .setExpiration(Date.from(LocalDateTime.now().plusSeconds(expire).atZone(ZoneId.systemDefault()).toInstant())) .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(JSONUtil.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; } }
package com.weiwei.xu.common; import lombok.*; import java.util.Date; /** * @author weiwei.xu * 为了方便后期获取token中的用户信息,将token中载荷部分单独封装成一个对象 */ @Setter @Getter @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode @ToString public class Payload<T> { private String id; private T userInfo; private Date expiration; }
server: port: 8080 servlet: context-path: /security spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.1.57:33060/security?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true username: root password: 123456 mybatis: mapper-locations: classpath:com/weiwei/xu/mapper/*.mapper.xml type-aliases-package: com.weiwei.xu.domain configuration: map-underscore-to-camel-case: true rsa: key: pubKeyFile: D:/auth_key/id_key_rsa.pub priKeyFile: D:/auth_key/id_key_rsa
package com.weiwei.xu.config; import com.weiwei.xu.utils.RsaUtils; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; import java.security.PrivateKey; import java.security.PublicKey; /** * @author weiwei.xu */ @Configuration @ConfigurationProperties(value = "rsa.key") public class RsaKeyProperties { private String pubKeyFile; private String priKeyFile; private PublicKey publicKey; private PrivateKey privateKey; 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; } @PostConstruct public void createResKey() throws Exception { publicKey = RsaUtils.getPublicKey(pubKeyFile); privateKey = RsaUtils.getPrivateKey(priKeyFile); } }
package com.weiwei.xu.config; import com.weiwei.xu.filter.JwtTokenFilter; import com.weiwei.xu.filter.JwtVerifyFilter; import com.weiwei.xu.service.SysUserService; 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; /** * SpringSecurity的配置类 * * @author weiwei.xu */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true) public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private SysUserService sysUserService; @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Autowired private RsaKeyProperties rsaKeyProperties; /** * 配置认证来源 * * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(sysUserService).passwordEncoder(passwordEncoder()); } /** * 配置过滤请求 * * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http. //关闭跨域站点请求 cors().and().csrf().disable().authorizeRequests() // swagger 文档 .antMatchers("/swagger-ui.html/**").permitAll() .antMatchers("/swagger-resources/**").permitAll() .antMatchers("/webjars/**").permitAll() .antMatchers("/*/api-docs").permitAll() // 文件 .antMatchers("/avatar/**").permitAll() .antMatchers("/file/**").permitAll() // 阿里巴巴 druid .antMatchers("/druid/**").permitAll() .anyRequest().authenticated() .and() //自定义认证过滤器 .addFilter(new JwtTokenFilter(authenticationManager(), rsaKeyProperties)) //自定义URL过滤器 .addFilter(new JwtVerifyFilter(authenticationManager(), rsaKeyProperties)) .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) ; } }
package com.weiwei.xu.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.bind.annotation.RestController; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.ParameterBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.schema.ModelRef; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Parameter; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.ArrayList; import java.util.List; /** * SwaggerConfig * * @author weiwei.xu */ @Configuration @EnableSwagger2 public class SwaggerConfig { static List<Parameter> parameters; static { /** * 这是为了我们在用 swagger 测试接口的时候添加头部信息 */ parameters = new ArrayList<>(); ParameterBuilder tokenPar = new ParameterBuilder(); tokenPar.name("Authorization").description("swagger测试用(模拟token传入)非必填 header").modelRef(new ModelRef("string")).parameterType("header").required(false); parameters.add(tokenPar.build()); } @Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() // .apis(RequestHandlerSelectors.basePackage("com.weiwei.xu.controller.ProductController")) .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class)) .paths(PathSelectors.ant("/**")) .build() .globalOperationParameters(parameters) .enable(true); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("SpringBoot整合SpringSecurity分布式版本") .termsOfServiceUrl("") .version("1.0") .build(); } }
package com.weiwei.xu.domain; import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.*; /** * 系统用户 * * @author weiwei.xu */ public class SysUser implements UserDetails { private Integer id; private String username; private String password; private Integer status; private List<SysRole> roles = new ArrayList<>(); @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } @Override public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public List<SysRole> getRoles() { return roles; } public void setRoles(List<SysRole> roles) { this.roles = roles; } @JsonIgnore @Override public Collection<? extends GrantedAuthority> getAuthorities() { return roles; } }
package com.weiwei.xu.domain; import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.security.core.GrantedAuthority; /** * 系统角色 * * @author weiwei.xu */ public class SysRole implements GrantedAuthority { private Integer id; private String roleName; private String roleDesc; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public String getRoleDesc() { return roleDesc; } public void setRoleDesc(String roleDesc) { this.roleDesc = roleDesc; } @JsonIgnore @Override public String getAuthority() { return roleName; } }
package com.weiwei.xu.mapper; import com.weiwei.xu.domain.SysUser; import org.apache.ibatis.annotations.*; import java.util.List; @Mapper public interface SysUserMapper { @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.weiwei.xu.mapper.SysRoleMapper.findByUid")) }) SysUser findByUsername(String username); }
package com.weiwei.xu.mapper; import com.weiwei.xu.domain.SysRole; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.Set; @Mapper public interface SysRoleMapper { /** * 根据uid查询用户所对应的角色 * * @param uid * @return */ @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}") Set<SysRole> findByUid(Integer uid); }
package com.weiwei.xu.service; import org.springframework.security.core.userdetails.UserDetailsService; public interface SysUserService extends UserDetailsService { }
package com.weiwei.xu.service.impl; import com.weiwei.xu.mapper.SysUserMapper; import com.weiwei.xu.service.SysUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @Transactional public class SysUserServiceImpl implements SysUserService { @Autowired private SysUserMapper sysUserMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return sysUserMapper.findByUsername(username); } }
package com.weiwei.xu.controller; import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @RestController @RequestMapping(value = "/product") public class ProductController { @GetMapping(value = "/findAll") @Secured({"ROLE_PRODUCT"}) public Map<String, Object> findAll() { Map<String, Object> map = new ConcurrentHashMap<>(); map.put("aa", "bb"); return map; } }
package com.weiwei.xu.filter; import com.fasterxml.jackson.databind.ObjectMapper; import com.weiwei.xu.config.RsaKeyProperties; import com.weiwei.xu.domain.SysRole; import com.weiwei.xu.domain.SysUser; import com.weiwei.xu.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.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * JWT的token Filter * * @author weiwei.xu */ public class JwtTokenFilter extends UsernamePasswordAuthenticationFilter { private RsaKeyProperties rsaKeyProperties; private AuthenticationManager authenticationManager; public JwtTokenFilter(AuthenticationManager authenticationManager, RsaKeyProperties rsaKeyProperties) { this.rsaKeyProperties = rsaKeyProperties; this.authenticationManager = authenticationManager; } /** * 试图认证的方法 * * @param request * @param response * @return * @throws AuthenticationException */ @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { try { SysUser sysUser = new ObjectMapper().readValue(request.getInputStream(), SysUser.class); UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword()); return authenticationManager.authenticate(usernamePasswordAuthenticationToken); } catch (Exception e) { try { //如果认证失败,提供自定义的json异常信息 response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); PrintWriter writer = response.getWriter(); Map<String, Object> resultMap = new ConcurrentHashMap<>(); resultMap.put("code", HttpServletResponse.SC_FORBIDDEN); resultMap.put("msg", "用户名或密码错误"); writer.write(new ObjectMapper().writeValueAsString(resultMap)); writer.flush(); writer.close(); } catch (Exception exception) { exception.printStackTrace(); } return null; } } /** * 认证成功之后,生成token,返回给前端 * * @param request * @param response * @param chain * @param authResult * @throws IOException * @throws ServletException */ @Override public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { String name = authResult.getName(); SysUser sysUser = new SysUser(); sysUser.setUsername(name); sysUser.setRoles((List<SysRole>) authResult.getAuthorities()); String token = JwtUtils.generateTokenExpireInMinutes(sysUser, rsaKeyProperties.getPrivateKey(), 24 * 60); //向客户端响应token response.addHeader("Authorization", "Bearer " + token); response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_OK); PrintWriter writer = response.getWriter(); Map<String, Object> resultMap = new ConcurrentHashMap<>(); resultMap.put("code", HttpServletResponse.SC_OK); resultMap.put("msg", "登录成功"); writer.write(new ObjectMapper().writeValueAsString(resultMap)); writer.flush(); writer.close(); } }
package com.weiwei.xu.filter; import com.fasterxml.jackson.databind.ObjectMapper; import com.weiwei.xu.common.Payload; import com.weiwei.xu.config.RsaKeyProperties; import com.weiwei.xu.domain.SysUser; import com.weiwei.xu.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.Map; import java.util.concurrent.ConcurrentHashMap; /** * @author weiwei.xu */ public class JwtVerifyFilter extends BasicAuthenticationFilter { private RsaKeyProperties rsaKeyProperties; public JwtVerifyFilter(AuthenticationManager authenticationManager, RsaKeyProperties rsaKeyProperties) { super(authenticationManager); this.rsaKeyProperties = rsaKeyProperties; } /** * 过滤请求 * * @param request * @param response * @param chain * @throws IOException * @throws ServletException */ @Override public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String header = request.getHeader("Authorization"); if (header == null || !header.startsWith("Bearer ")) { chain.doFilter(request, response); // try { // response.setContentType("application/json;charset=utf-8"); // response.setStatus(HttpServletResponse.SC_FORBIDDEN); // PrintWriter writer = response.getWriter(); // Map<String, Object> resultMap = new ConcurrentHashMap<>(); // resultMap.put("code", HttpServletResponse.SC_FORBIDDEN); // resultMap.put("msg", "请登录"); // writer.write(new ObjectMapper().writeValueAsString(resultMap)); // writer.flush(); // writer.close(); // } catch (IOException e) { // e.printStackTrace(); // } return; } else { String token = header.replace("Bearer ", ""); Payload<SysUser> sysUserPayload = null; try { sysUserPayload = JwtUtils.getInfoFromToken(token, rsaKeyProperties.getPublicKey(), SysUser.class); } catch (Exception e) { try { //如果认证失败,提供自定义的json异常信息 response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); PrintWriter writer = response.getWriter(); Map<String, Object> resultMap = new ConcurrentHashMap<>(); resultMap.put("code", HttpServletResponse.SC_FORBIDDEN); resultMap.put("msg", "token解析错误"); writer.write(new ObjectMapper().writeValueAsString(resultMap)); writer.flush(); writer.close(); } catch (Exception exception) { exception.printStackTrace(); } } SysUser sysUser = sysUserPayload.getUserInfo(); if (null != sysUser) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword(), sysUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); } } } }