前言,毕设项目中,由于只考虑web端的,没考虑客户端,所以没有用oauth2(其实也是因为没掌握,尴尬)
一、项目准备
-
父项目,建立统一管理,pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jiangming</groupId> <artifactId>hg_blog</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>pom</packaging> <description>a blog web</description> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <junit.version>4.12</junit.version> <log4j.version>1.2.17</log4j.version> <lombok.version>1.18.20</lombok.version> <mysql.version>8.0.26</mysql.version> <druid.version>1.2.5</druid.version> <alibaba.fastjson.version>1.2.61</alibaba.fastjson.version> <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version> <spring-boot.version>2.3.2.RELEASE</spring-boot.version> <spring-cloud.version>Hoxton.SR8</spring-cloud.version> <spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${alibaba.fastjson.version}</version> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> <addResources>true</addResources> </configuration> </plugin> </plugins> </build> </project>
-
子项目pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>hg_blog</artifactId> <groupId>com.jiangming</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>hegu-auth-jwt</artifactId> <dependencies> <!--自定义公共包--> <dependency> <groupId>com.jiangming</groupId> <artifactId>hegu-api-commons</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</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> </dependency> </dependencies> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> </project>
-
项目结构
-
nacos配置文件
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://IP地址:3302/数据库名?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC&characterEncoding=utf-8 username: root password: 123456 druid: driver-class-name: com.mysql.cj.jdbc.Driver initial-size: 10 max-active: 100 min-idle: 10 max-wait: 1000 management: endpoints: web: exposure: include: '*'
二、代码
-
application.yml
spring: profiles: active: dev
-
bootstrap.yml
server: port: 端口号 spring: application: name: 服务名 cloud: nacos: discovery: server-addr: nacos地址 #命名空间 namespace: ce58f19d-b36c-4aad-968a-1a1f2280cfc config: server-addr: nacos地址 #命名空间 namespace: ce58f19d-b36c-4aad-968a-1a1f2280cfc file-extension: yaml
-
主启动类
package com.jc.springcloud; /** * @program: hg_blog * @description: * @author: hjc * @create: 2022-01-02 19:47 **/ @SpringBootApplication @EnableDiscoveryClient public class HeguAuthJwtMain { public static void main(String[] args){ SpringApplication.run(HeguAuthJwtMain.class,args); } }
-
JwtUtil工具类
package com.jc.springcloud.common; /** * @program: hg_blog * @description: 生成token * @author: hjc * @create: 2022-01-04 13:31 **/ public class JwtUtil { private static final String JWT_PAYLOAD_USER_KEY = "user"; // 私钥,RSA在线生成工具 private static final String PRIVATE_KEY =""; /*** * @description 私钥 * @return java.security.PrivateKey * @author hjc * CreateDate 2022/1/5 15:15 */ public static PrivateKey getPrivateKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(new BASE64Decoder().decodeBuffer(PRIVATE_KEY)); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePrivate(spec); } /*** * @description 生成token * @param userInfo 用户信息 * @param privateKey 私钥对象 * @param expire 过期时间 * @return java.lang.String * @author hjc * CreateDate 2022/1/4 13:34 */ public static <T> String createToken(T userInfo, PrivateKey privateKey,int expire) throws JsonProcessingException{ //设置过期时间 //获取当前时间 Calendar calendar = Calendar.getInstance(); //在当前时间加上过期时间 calendar.add(Calendar.SECOND,expire); //将userInfo对象转为Json String userInfoJson = new ObjectMapper().writeValueAsString(userInfo); //生成token return Jwts.builder()//获得Jwt的编译对象 .claim(JWT_PAYLOAD_USER_KEY,userInfoJson)//设置负载,负载内容为JSON字符串 .setExpiration(calendar.getTime())//设置过期时间 .signWith(SignatureAlgorithm.RS256, privateKey)//设置签名,使用私钥做为签名 .compact();//生成token } }
-
SysUserDetails扩展类
package com.jc.springcloud.common; /** * @program: hg_blog * @description: * @author: hjc * @create: 2022-01-03 14:40 **/ @Data @AllArgsConstructor @NoArgsConstructor public class SysUserDetails implements UserDetails { //扩展字段 private Integer user_id; //默认字段 private String username; private String password; private boolean isAccountNonLocked; private Collection<? extends GrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
-
SecurityConfig类
package com.jc.springcloud.config; /** * @program: hg_blog * @description: Security配置 * @author: hjc * @create: 2022-01-02 19:50 **/ @Configuration @EnableWebSecurity @Slf4j public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http .cors()//开启security对跨域的支持 .and() .formLogin()//登录设置 .loginPage("/login")//登录处理器 .and() .csrf() .disable();//禁用跨域伪造检测 http.addFilter(new BlogUsernamePasswordAuthenticationFilter(authenticationManager())); } }
-
BlogUsernamePasswordAuthenticationFilter
package com.jc.springcloud.filter; /** * @program: hg_blog * @description: 当用户登录时会自动执行该过滤器 * @author: hjc * @create: 2022-01-04 14:26 **/ @Slf4j public class BlogUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { //过期时间一天 private final int ACTIVE_TIME = 60 * 60 * 24; // private final int ACTIVE_TIME = 60 * 2; //设置认证管理器属性 private AuthenticationManager authenticationManager; public BlogUsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager){ this.authenticationManager = authenticationManager; } /*** * @description 用于获取客户端提交的数据,提交的数据处理后会交给UserDetailsService继续执行 * @param request * @param response * @return org.springframework.security.core.Authentication * @author hjc * CreateDate 2022/1/4 14:45 */ @SneakyThrows @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { log.info("attemptAuthentication被执行"); //获取客户端传入的payload参数 LoginInfo loginInfo = new ObjectMapper().readValue(request.getInputStream(), LoginInfo.class); log.info(String.valueOf(loginInfo)); //创建认证令牌并将认证信息传入 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginInfo.getUsername(), loginInfo.getPassword()); //将认证令牌提交给认证管理器,认证管理器会将authRequest交给UserDetailsService处理 return authenticationManager.authenticate(authenticationToken); } /*** * @description 当用户登录成功后执行该方法,它在UserDetailsService认证成功后执行 * 在该方法中使用私钥生成token,并将token发送到客户端 * @param request * @param response * @param chain * @param authResult * @return void * @author hjc * CreateDate 2022/1/4 14:48 */ @SneakyThrows @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { log.info("登录成功!"); response.setContentType("application/json;charset=utf-8"); //从认证信息中获取用户数据,并设置在payload集合中 SysUserDetails sysUserDetails = (SysUserDetails) authResult.getPrincipal(); log.info(String.valueOf(sysUserDetails)); //创建jwt负载的map集合并设置相关数据 Map<String, Object> payloadMap = new HashMap<String, Object>(){{ put("user_id",sysUserDetails.getUser_id());//当前登录者的id put("auth",sysUserDetails.getAuthorities());//当前账户的权限集合 }}; //获取私钥 PrivateKey privateKey = JwtUtil.getPrivateKey(); //使用私钥和过期时间生成token String token = JwtUtil.createToken(payloadMap,privateKey,ACTIVE_TIME); PrintWriter writer = response.getWriter(); writer.print(JSON.toJSONString(CommonResult.success("登录成功!",token))); writer.flush(); writer.close(); } /*** * @description 用户登录失败后执行该方法,它在UserDetailsService认证失败后执行 * @param request * @param response * @param failed * @return void * @author hjc * CreateDate 2022/1/4 15:55 */ @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { log.info("登录失败,unsuccessfulAuthentication"); response.setContentType("application/json;charset=utf-8"); PrintWriter writer = response.getWriter(); writer.print(JSON.toJSONString(CommonResult.fail(HttpServletResponse.SC_UNAUTHORIZED,failed.getMessage()))); writer.flush(); writer.close(); } }
-
AuthMapper
package com.jc.springcloud.mapper; /** * @program: hg_blog * @description: * @author: hjc * @create: 2022-01-03 12:35 **/ @Mapper public interface AuthMapper { //登录 @Select("select user_id,user_phone,user_password,is_superuser,is_lock from hb_user where user_phone = #{user_phone}") public HbUserInfo queryLoginByPhone(String user_phone); }
-
SysUserDetailsServiceImpl
package com.jc.springcloud.service.impl; /** * @program: hg_blog * @description: UserDetailsService实现类,该类交给Spring管理后,Security会在登录时自动查找UserDetailsService的实现类 * @author: hjc * @create: 2022-01-03 14:47 **/ @Slf4j @Service public class SysUserDetailsServiceImpl implements UserDetailsService { @Resource private AuthMapper authMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { log.info("执行UserDetailsServiceImpl"); //根据用户名及手机号查询用户信息 HbUserInfo hbUserInfo = authMapper.queryLoginByPhone(username); if (hbUserInfo == null){ throw new UsernameNotFoundException("用户不存在!"); } Collection<GrantedAuthority> authorities = new ArrayList<>(); if (hbUserInfo.getIs_superuser() == 0){ authorities.add(new SimpleGrantedAuthority("ROLE_USER")); }else { authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); } return new SysUserDetails( hbUserInfo.getUser_id(), hbUserInfo.getUser_phone(), hbUserInfo.getUser_password(), hbUserInfo.getIs_lock() == 0 ? true : false, authorities); } }