diff --git a/im-platform/pom.xml b/im-platform/pom.xml index c8309c9..c6c3dc5 100644 --- a/im-platform/pom.xml +++ b/im-platform/pom.xml @@ -106,6 +106,12 @@ mybatis-plus-generator 3.3.2 + + + com.auth0 + java-jwt + 3.11.0 + diff --git a/im-platform/src/main/java/com/bx/implatform/ImplatformApp.java b/im-platform/src/main/java/com/bx/implatform/ImplatformApp.java index 15480c2..baf7338 100644 --- a/im-platform/src/main/java/com/bx/implatform/ImplatformApp.java +++ b/im-platform/src/main/java/com/bx/implatform/ImplatformApp.java @@ -4,13 +4,14 @@ import lombok.extern.slf4j.Slf4j; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Slf4j @EnableAspectJAutoProxy(exposeProxy = true) @MapperScan(basePackages = {"com.bx.implatform.mapper"}) -@SpringBootApplication +@SpringBootApplication(exclude= {SecurityAutoConfiguration.class })// 禁用secrity public class ImplatformApp { public static void main(String[] args) { diff --git a/im-platform/src/main/java/com/bx/implatform/config/MvcConfig.java b/im-platform/src/main/java/com/bx/implatform/config/MvcConfig.java new file mode 100644 index 0000000..5e84cce --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/config/MvcConfig.java @@ -0,0 +1,36 @@ +package com.bx.implatform.config; + +import com.bx.implatform.interceptor.AuthInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + + +@Configuration +public class MvcConfig implements WebMvcConfigurer { + + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(authInterceptor()) + .addPathPatterns("/**") + .excludePathPatterns( "/image/upload","/login","/logout","/register", + "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"); + } + + + @Bean + public AuthInterceptor authInterceptor() { + return new AuthInterceptor(); + } + + @Bean + public PasswordEncoder passwordEncoder(){ + // 使用BCrypt加密密码 + return new BCryptPasswordEncoder(); + } + +} diff --git a/im-platform/src/main/java/com/bx/implatform/config/WebSecurityConfg.java b/im-platform/src/main/java/com/bx/implatform/config/WebSecurityConfg.java deleted file mode 100644 index 7ef0d62..0000000 --- a/im-platform/src/main/java/com/bx/implatform/config/WebSecurityConfg.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.bx.implatform.config; - -import com.alibaba.fastjson.JSON; -import com.bx.implatform.enums.ResultCode; -import com.bx.implatform.result.Result; -import com.bx.implatform.result.ResultUtils; -import com.bx.implatform.service.IUserService; -import com.bx.implatform.session.UserSession; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.*; -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.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; - -import java.io.PrintWriter; - -/* - * SpringSecurity安全框架配置 - * - * @Author Blue - * @Date 2022/10/21 - */ -@Slf4j -@Configuration -@EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled=true) -public class WebSecurityConfg extends WebSecurityConfigurerAdapter { - - - - @Qualifier("securityUserDetailsServiceImpl") - @Autowired - private UserDetailsService userDetailsService; - - @Autowired - private IUserService userService; - - @Override - protected void configure(HttpSecurity http) throws Exception { - http.authorizeRequests() - .antMatchers("/image/upload","/login","/logout","/register","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**") - .permitAll() - .anyRequest() //任何其它请求 - .authenticated() //都需要身份认证 - .and() - // 登录配置表单认证方式 - .formLogin() - .usernameParameter("username")//设置登录账号参数,与表单参数一致 - .passwordParameter("password")//设置登录密码参数,与表单参数一致 - .loginProcessingUrl("/login")//配置默认登录入口 - .successHandler(successHandler()) - .failureHandler(failureHandler()) - .and() - // 注销 - .logout() - .logoutUrl("/logout") - .logoutSuccessHandler(logoutHandler()) - .permitAll() - .and() - // session管理 - .sessionManagement() - .and() - // 禁用跨站csrf攻击防御 - .csrf() - .disable() - .exceptionHandling() - .authenticationEntryPoint(entryPoint()); - - } - - @Bean - AuthenticationFailureHandler failureHandler(){ - return (request, response, exception) -> { - response.setContentType("application/json;charset=utf-8"); - PrintWriter out = response.getWriter(); - Result result = ResultUtils.error(ResultCode.LOGIN_ERROR,exception.getMessage()); - if (exception instanceof LockedException) { - result =ResultUtils.error(ResultCode.LOGIN_ERROR,"账户被锁定,请联系管理员!"); - } else if (exception instanceof CredentialsExpiredException) { - result = ResultUtils.error(ResultCode.LOGIN_ERROR,"密码过期,请联系管理员!"); - } else if (exception instanceof AccountExpiredException) { - result =ResultUtils.error(ResultCode.LOGIN_ERROR,"账户过期,请联系管理员!"); - } else if (exception instanceof DisabledException) { - result = ResultUtils.error(ResultCode.LOGIN_ERROR,"账户被禁用,请联系管理员!"); - } else if (exception instanceof BadCredentialsException) { - result =ResultUtils.error(ResultCode.LOGIN_ERROR,"用户名或者密码输入错误,请重新输入!"); - } - out.write(new ObjectMapper().writeValueAsString(result)); - out.flush(); - out.close(); - }; - } - - @Bean - AuthenticationSuccessHandler successHandler(){ - return (request, response, authentication) -> { - User useDetail = (User)authentication.getPrincipal(); - String strJson = useDetail.getUsername(); - UserSession userSession = JSON.parseObject(strJson,UserSession.class); - log.info("用户 '{}' 登录,id:{},昵称:{}",userSession.getUserName(),userSession.getId(),userSession.getNickName()); - // 响应 - response.setContentType("application/json;charset=utf-8"); - PrintWriter out = response.getWriter(); - Result result = ResultUtils.success(); - out.write(new ObjectMapper().writeValueAsString(result)); - out.flush(); - out.close(); - - }; - } - - - @Bean - LogoutSuccessHandler logoutHandler(){ - return (request, response, authentication) -> { - User useDetail = (User)authentication.getPrincipal(); - String strJson = useDetail.getUsername(); - UserSession userSession = JSON.parseObject(strJson,UserSession.class); - log.info("用户 '{}' 退出,id:{},昵称:{}",userSession.getUserName(),userSession.getId(),userSession.getNickName()); - // 响应 - response.setContentType("application/json;charset=utf-8"); - PrintWriter out = response.getWriter(); - Result result = ResultUtils.success(); - out.write(new ObjectMapper().writeValueAsString(result)); - out.flush(); - out.close(); - }; - } - - @Bean - AuthenticationEntryPoint entryPoint(){ - return (request, response, exception) -> { - response.setContentType("application/json;charset=utf-8"); - PrintWriter out = response.getWriter(); - Result result = ResultUtils.error(ResultCode.NO_LOGIN); - out.write(new ObjectMapper().writeValueAsString(result)); - out.flush(); - out.close(); - }; - } - - - - @Bean - public PasswordEncoder passwordEncoder(){ - // 使用BCrypt加密密码 - return new BCryptPasswordEncoder(); - } - - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); - } - - -} diff --git a/im-platform/src/main/java/com/bx/implatform/controller/RegisterController.java b/im-platform/src/main/java/com/bx/implatform/controller/LoginController.java similarity index 60% rename from im-platform/src/main/java/com/bx/implatform/controller/RegisterController.java rename to im-platform/src/main/java/com/bx/implatform/controller/LoginController.java index a1250b8..c4fab21 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/RegisterController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/LoginController.java @@ -1,9 +1,11 @@ package com.bx.implatform.controller; +import com.bx.implatform.enums.ResultCode; import com.bx.implatform.result.Result; import com.bx.implatform.result.ResultUtils; import com.bx.implatform.service.IUserService; +import com.bx.implatform.vo.LoginVO; import com.bx.implatform.vo.RegisterVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -16,17 +18,25 @@ import javax.validation.Valid; -@Api(tags = "用户注册") +@Api(tags = "用户登录和注册") @RestController -public class RegisterController { +public class LoginController { @Autowired private IUserService userService; + @PostMapping("/login") + @ApiOperation(value = "用户注册",notes="用户注册") + public Result register(@Valid @RequestBody LoginVO vo){ + String token = userService.login(vo); + return ResultUtils.success(token,ResultCode.SUCCESS.getMsg()); + } + + @PostMapping("/register") @ApiOperation(value = "用户注册",notes="用户注册") - public Result register(@Valid @RequestBody RegisterVO dto){ - userService.register(dto); + public Result register(@Valid @RequestBody RegisterVO vo){ + userService.register(vo); return ResultUtils.success(); } } diff --git a/im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java b/im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java index bc6f96c..2a1f5b1 100644 --- a/im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java +++ b/im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java @@ -11,14 +11,9 @@ public enum ResultCode { SUCCESS(200,"成功"), LOGIN_ERROR(400,"登录异常"), NO_LOGIN(401,"未登录"), - FORBIDDEN(403,"禁止访问"), - NOT_FIND(404,"无法找到文件"), PROGRAM_ERROR(500,"系统繁忙,请稍后再试"), PASSWOR_ERROR(10001,"密码不正确"), - VERITY_CODE_NOT_EXIST(10002,"验证码不存在"), - VERITY_CODE_ERROR(10003,"验证码不正确"), - USERNAME_ALREADY_REGISTER(10004,"该用户名已注册"), - MOBILE_ALREADY_REGISTER(10005,"该手机号码已注册"), + USERNAME_ALREADY_REGISTER(10003,"该用户名已注册"), ; diff --git a/im-platform/src/main/java/com/bx/implatform/interceptor/AuthInterceptor.java b/im-platform/src/main/java/com/bx/implatform/interceptor/AuthInterceptor.java new file mode 100644 index 0000000..edea946 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/interceptor/AuthInterceptor.java @@ -0,0 +1,37 @@ +package com.bx.implatform.interceptor; + + +import com.alibaba.fastjson.JSON; +import com.bx.implatform.enums.ResultCode; +import com.bx.implatform.exception.GlobalException; +import com.bx.implatform.session.UserSession; +import com.bx.implatform.util.JwtUtil; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +; + +public class AuthInterceptor implements HandlerInterceptor { + + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + //如果不是映射到方法直接通过 + if (!(handler instanceof HandlerMethod)) { + return true; + } + //从 http 请求头中取出 token + String token = request.getHeader("token"); + if (token == null) { + throw new GlobalException(ResultCode.NO_LOGIN); + } + //验证 token + JwtUtil.checkSign(token); + // 存放session + String strJson = JwtUtil.getInfo(token); + UserSession userSession = JSON.parseObject(strJson,UserSession.class); + request.setAttribute("session",userSession); + return true; + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/service/IUserService.java b/im-platform/src/main/java/com/bx/implatform/service/IUserService.java index b45d43f..5706b42 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/IUserService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/IUserService.java @@ -2,6 +2,7 @@ package com.bx.implatform.service; import com.baomidou.mybatisplus.extension.service.IService; import com.bx.implatform.entity.User; +import com.bx.implatform.vo.LoginVO; import com.bx.implatform.vo.RegisterVO; import com.bx.implatform.vo.UserVO; @@ -10,7 +11,9 @@ import java.util.List; public interface IUserService extends IService { - void register(RegisterVO registerDTO); + String login(LoginVO vo); + + void register(RegisterVO vo); User findUserByName(String username); diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java index 644b80b..0f6d0db 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java @@ -1,5 +1,6 @@ package com.bx.implatform.service.impl; +import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bx.imcommon.contant.RedisKey; @@ -15,6 +16,8 @@ import com.bx.implatform.service.IUserService; import com.bx.implatform.session.SessionContext; import com.bx.implatform.session.UserSession; import com.bx.implatform.util.BeanUtils; +import com.bx.implatform.util.JwtUtil; +import com.bx.implatform.vo.LoginVO; import com.bx.implatform.vo.RegisterVO; import com.bx.implatform.vo.UserVO; import lombok.extern.slf4j.Slf4j; @@ -45,6 +48,29 @@ public class UserServiceImpl extends ServiceImpl implements IU @Autowired private IFriendService friendService; + /** + * 用户登录 + * + * @param vo 注册vo + * @return + */ + + @Override + public String login(LoginVO vo) { + User user = findUserByName(vo.getUserName()); + if(null == user){ + throw new GlobalException(ResultCode.PROGRAM_ERROR,"用户不存在"); + } + if(!passwordEncoder.matches(vo.getPassword(),user.getPassword())){ + throw new GlobalException(ResultCode.PASSWOR_ERROR); + } + // 生成token + UserSession session = BeanUtils.copyProperties(user,UserSession.class); + String strJson = JSON.toJSONString(session); + String token = JwtUtil.sign(user.getId(),strJson); + return token; + } + /** * 用户注册 * diff --git a/im-platform/src/main/java/com/bx/implatform/session/SessionContext.java b/im-platform/src/main/java/com/bx/implatform/session/SessionContext.java index d21cf81..b9a0c63 100644 --- a/im-platform/src/main/java/com/bx/implatform/session/SessionContext.java +++ b/im-platform/src/main/java/com/bx/implatform/session/SessionContext.java @@ -1,8 +1,9 @@ package com.bx.implatform.session; -import com.alibaba.fastjson.JSON; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.User; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; /* * @Description @@ -13,11 +14,12 @@ public class SessionContext { public static UserSession getSession(){ - User useDetail = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - String strJson = useDetail.getUsername(); - UserSession userSession = JSON.parseObject(strJson,UserSession.class); + // 从请求上下文里获取Request对象 + ServletRequestAttributes requestAttributes = ServletRequestAttributes.class. + cast(RequestContextHolder.getRequestAttributes()); + HttpServletRequest request = requestAttributes.getRequest(); + UserSession userSession = (UserSession) request.getAttribute("session"); return userSession; } - } diff --git a/im-platform/src/main/java/com/bx/implatform/util/JwtUtil.java b/im-platform/src/main/java/com/bx/implatform/util/JwtUtil.java new file mode 100644 index 0000000..2c27a83 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/util/JwtUtil.java @@ -0,0 +1,94 @@ +package com.bx.implatform.util; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.bx.implatform.exception.GlobalException; +; + +import java.util.Date; +import java.util.Map; + +public class JwtUtil { + /** + * 过期5分钟 + * */ + private static final long EXPIRE_TIME = 30 * 60 * 1000; + + /** + * jwt密钥 + * */ + private static final String SECRET = "MIIBIjANBgkq"; + + /** + * 生成jwt字符串,30分钟后过期 JWT(json web token) + * @param userId + * @param info + * @return + * */ + public static String sign(Long userId, String info) { + try { + Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); + Algorithm algorithm = Algorithm.HMAC256(SECRET); + return JWT.create() + //将userId保存到token里面 + .withAudience(userId.toString()) + //存放自定义数据 + .withClaim("info", info) + //五分钟后token过期 + .withExpiresAt(date) + //token的密钥 + .sign(algorithm); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 根据token获取userId + * @param token + * @return + * */ + public static Long getUserId(String token) { + try { + String userId = JWT.decode(token).getAudience().get(0); + return Long.parseLong(userId); + }catch (JWTDecodeException e) { + return null; + } + } + + /** + * 根据token获取自定义数据info + * @param token + * @return + * */ + public static String getInfo(String token) { + try { + return JWT.decode(token).getClaim("info").asString(); + }catch (JWTDecodeException e) { + return null; + } + } + + /** + * 校验token + * @param token + * @return + * */ + public static boolean checkSign(String token) { + try { + Algorithm algorithm = Algorithm.HMAC256(SECRET); + JWTVerifier verifier = JWT.require(algorithm) + //.withClaim("username, username) + .build(); + verifier.verify(token); + return true; + }catch (JWTVerificationException e) { + throw new GlobalException("token 无效,请重新获取"); + } + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/vo/LoginVO.java b/im-platform/src/main/java/com/bx/implatform/vo/LoginVO.java new file mode 100644 index 0000000..ad2a841 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/LoginVO.java @@ -0,0 +1,22 @@ +package com.bx.implatform.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotEmpty; + +@Data +@ApiModel("用户登录VO") +public class LoginVO { + + //@NotEmpty(message="用户名不可为空") + @ApiModelProperty(value = "用户名") + private String userName; + + // @NotEmpty(message="用户密码不可为空") + @ApiModelProperty(value = "用户密码") + private String password; + +} diff --git a/im-ui/src/api/httpRequest.js b/im-ui/src/api/httpRequest.js index bd93ec1..020eab4 100644 --- a/im-ui/src/api/httpRequest.js +++ b/im-ui/src/api/httpRequest.js @@ -15,7 +15,10 @@ const http = axios.create({ * 请求拦截 */ http.interceptors.request.use(config => { - // todo 请求头带上token + let token = sessionStorage.getItem("token"); + if (token) { + config.headers.token = sessionStorage.getItem("token"); + } return config }, error => { return Promise.reject(error) diff --git a/im-ui/src/view/Home.vue b/im-ui/src/view/Home.vue index a3b8b89..06359c1 100644 --- a/im-ui/src/view/Home.vue +++ b/im-ui/src/view/Home.vue @@ -190,13 +190,9 @@ this.playAudioTip(); }, handleExit() { - this.$http({ - url: "/logout", - method: 'get' - }).then(() => { - this.$wsApi.closeWebSocket(); - location.href = "/"; - }) + this.$wsApi.closeWebSocket(); + sessionStorage.removeItem("token"); + location.href = "/"; }, playAudioTip(){ let audio = new Audio(); diff --git a/im-ui/src/view/Login.vue b/im-ui/src/view/Login.vue index c25fa33..45ff95a 100644 --- a/im-ui/src/view/Login.vue +++ b/im-ui/src/view/Login.vue @@ -2,8 +2,8 @@