diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..20cdfa9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,115 @@ + + + 4.0.0 + cn.edu.ecnu.stu + bookstore + 0.0.1-SNAPSHOT + bookstore + bookstore + + 1.8 + UTF-8 + UTF-8 + 2.6.13 + + + + org.springframework.boot + spring-boot-starter-web + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.2.2 + + + + com.mysql + mysql-connector-j + runtime + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-security + + + com.alibaba + druid + 1.1.9 + + + com.auth0 + java-jwt + 3.4.0 + + + com.alibaba + fastjson + 1.2.47 + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.apache.httpcomponents + httpclient + 4.5.13 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + UTF-8 + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + cn.edu.ecnu.stu.bookstore.BookstoreApplication + true + + + + repackage + + repackage + + + + + + + + diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/BookstoreApplication.java b/src/main/java/cn/edu/ecnu/stu/bookstore/BookstoreApplication.java new file mode 100644 index 0000000..395bddf --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/BookstoreApplication.java @@ -0,0 +1,13 @@ +package cn.edu.ecnu.stu.bookstore; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class BookstoreApplication { + + public static void main(String[] args) { + SpringApplication.run(BookstoreApplication.class, args); + } + +} diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/component/AppException.java b/src/main/java/cn/edu/ecnu/stu/bookstore/component/AppException.java new file mode 100644 index 0000000..10324e5 --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/component/AppException.java @@ -0,0 +1,28 @@ +package cn.edu.ecnu.stu.bookstore.component; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class AppException extends RuntimeException{ + + private String code; + + private String message; + + public void setCode(String code) { + this.code = code; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/component/Constants.java b/src/main/java/cn/edu/ecnu/stu/bookstore/component/Constants.java new file mode 100644 index 0000000..ac2014c --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/component/Constants.java @@ -0,0 +1,18 @@ +package cn.edu.ecnu.stu.bookstore.component; + +public interface Constants { + + String SYSTEM_ERROR = "500"; + String CLIENT_ERROR = "400"; + String SUCCESS = "200"; + String AUTHENTICATION_ERROR = "401"; + String UNAUTHORIZED_ERROR = "403"; + + String REGISTER_ERROR_MESSAGE = "用户名已存在"; + String PARAMETER_ERROR_MESSAGE = "参数非法"; + String SUCCESS_MESSAGE = "ok"; + String AUTHENTICATION_ERROR_MESSAGE = "用户未认证或token过期,请先登录"; + String PASSWORD_ERROR = "用户名不存在或密码错误"; + + String URL_PREFIX = "http://127.0.0.1:8080"; +} diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/component/Result.java b/src/main/java/cn/edu/ecnu/stu/bookstore/component/Result.java new file mode 100644 index 0000000..bdee5e1 --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/component/Result.java @@ -0,0 +1,46 @@ +package cn.edu.ecnu.stu.bookstore.component; + +import lombok.Data; + +@Data +public class Result { + + private Object data; + + private String message; + + private String code; + + public Result(String code, String message, Object data) { + this.code = code; + this.message = message; + this.data = data; + } + + @Override + public String toString() { + return "Result{" + + "data=" + data + + ", message='" + message + '\'' + + ", code='" + code + '\'' + + '}'; + } + + public static Result success(Object data) { + return new Result(Constants.SUCCESS, "ok", data); + } + + public static Result error(String code, String message) { + return new Result(code, message, null); + } + + public static Result error(String code, String message, Object data) { + return new Result(code, message, data); + } + + public static Result success() { + return new Result(Constants.SUCCESS, Constants.SUCCESS_MESSAGE, null); + } + + +} diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/config/RedisConfig.java b/src/main/java/cn/edu/ecnu/stu/bookstore/config/RedisConfig.java new file mode 100644 index 0000000..23d7b7d --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/config/RedisConfig.java @@ -0,0 +1,31 @@ +package cn.edu.ecnu.stu.bookstore.config; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisConfig implements InitializingBean { + + @Autowired + private RedisTemplate redisTemplate; + + @Override + public void afterPropertiesSet() throws Exception { + RedisSerializer stringSerializer = new StringRedisSerializer(); + //key序列化方式 + redisTemplate.setKeySerializer(stringSerializer); + //String的序列化方式 + redisTemplate.setStringSerializer(stringSerializer); + //value序列化方式 + redisTemplate.setValueSerializer(stringSerializer); + //hash key序列化方式 + redisTemplate.setHashKeySerializer(stringSerializer); + //hash value序列化方式 + redisTemplate.setHashValueSerializer(stringSerializer); + } +} diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/config/SecurityConfig.java b/src/main/java/cn/edu/ecnu/stu/bookstore/config/SecurityConfig.java new file mode 100644 index 0000000..3b7ab6b --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/config/SecurityConfig.java @@ -0,0 +1,51 @@ +package cn.edu.ecnu.stu.bookstore.config; + +import cn.edu.ecnu.stu.bookstore.filter.AuthenticationFilter; +import cn.edu.ecnu.stu.bookstore.handler.AuthenticationEntryPointImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +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.UsernamePasswordAuthenticationFilter; + +@Configuration +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Autowired + public AuthenticationFilter authenticationFilter; + + @Bean + public AuthenticationEntryPoint authenticationEntryPoint() { + return new AuthenticationEntryPointImpl(); + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + .antMatchers("/auth/*").permitAll() + .anyRequest().authenticated(); + + http.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class); + http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint()); + } +} diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/controller/AuthController.java b/src/main/java/cn/edu/ecnu/stu/bookstore/controller/AuthController.java new file mode 100644 index 0000000..f14793a --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/controller/AuthController.java @@ -0,0 +1,54 @@ +package cn.edu.ecnu.stu.bookstore.controller; + +import cn.edu.ecnu.stu.bookstore.component.Result; +import cn.edu.ecnu.stu.bookstore.pojo.User; +import cn.edu.ecnu.stu.bookstore.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/auth") +public class AuthController { + + @Autowired + private UserService userService; + + @PostMapping("/register") + public Result register(@RequestBody User user) { + userService.register(user); + return Result.success(); + } + + @PostMapping("/unregister") + public Result unregister(@RequestBody User user) { + userService.unregister(user); + return Result.success(); + } + + @PostMapping("/login") + public Result login(@RequestBody User user) { + String token = userService.login(user); + HashMap map = new HashMap<>(); + map.put("token", token); + return Result.success(map); + } + + @PostMapping("/password") + public Result changePassword(@RequestBody Map map) { + String username = map.get("username"); + String oldPassword = map.get("oldPassword"); + String newPassword = map.get("newPassword"); + userService.changePassword(username, oldPassword, newPassword); + return Result.success(); + } + + @PostMapping("/logout") + public Result logout(@RequestBody Map map) { + userService.logout(map.get("username")); + return Result.success(); + } +} + diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/controller/TestController.java b/src/main/java/cn/edu/ecnu/stu/bookstore/controller/TestController.java new file mode 100644 index 0000000..8714da3 --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/controller/TestController.java @@ -0,0 +1,15 @@ +package cn.edu.ecnu.stu.bookstore.controller; + +import cn.edu.ecnu.stu.bookstore.component.Result; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + + @GetMapping("/test") + public Result test() { + return Result.success("hello, test"); + } +} diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/filter/AuthenticationFilter.java b/src/main/java/cn/edu/ecnu/stu/bookstore/filter/AuthenticationFilter.java new file mode 100644 index 0000000..54f0140 --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/filter/AuthenticationFilter.java @@ -0,0 +1,46 @@ +package cn.edu.ecnu.stu.bookstore.filter; + +import cn.edu.ecnu.stu.bookstore.pojo.User; +import cn.edu.ecnu.stu.bookstore.utils.JwtUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class AuthenticationFilter extends OncePerRequestFilter { + + @Autowired + private RedisTemplate redisTemplate; + + private boolean hasLogin(Integer userId) { + String s = (String)redisTemplate.opsForValue().get("userId:" + userId); + return StringUtils.hasText(s); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String token = request.getHeader("token"); + if(!StringUtils.hasText(token)){ + filterChain.doFilter(request, response); + return; + } + User user = (User) JwtUtil.getTokenInfo(token, User.class); + if(!hasLogin(user.getId())) { + filterChain.doFilter(request, response); + return; + } + UsernamePasswordAuthenticationToken token1 = new UsernamePasswordAuthenticationToken(user, null, null); + SecurityContextHolder.getContext().setAuthentication(token1); + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/handler/AuthenticationEntryPointImpl.java b/src/main/java/cn/edu/ecnu/stu/bookstore/handler/AuthenticationEntryPointImpl.java new file mode 100644 index 0000000..5c9fa3d --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/handler/AuthenticationEntryPointImpl.java @@ -0,0 +1,22 @@ +package cn.edu.ecnu.stu.bookstore.handler; + +import cn.edu.ecnu.stu.bookstore.component.Constants; +import cn.edu.ecnu.stu.bookstore.component.Result; +import com.alibaba.fastjson.JSON; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + response.setStatus(200); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().print(JSON.toJSONString(Result.error(Constants.AUTHENTICATION_ERROR, Constants.AUTHENTICATION_ERROR_MESSAGE))); + } +} diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/handler/GlobalExceptionHandler.java b/src/main/java/cn/edu/ecnu/stu/bookstore/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..bd2b425 --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/handler/GlobalExceptionHandler.java @@ -0,0 +1,24 @@ +package cn.edu.ecnu.stu.bookstore.handler; + +import cn.edu.ecnu.stu.bookstore.component.AppException; +import cn.edu.ecnu.stu.bookstore.component.Constants; +import cn.edu.ecnu.stu.bookstore.component.Result; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(AppException.class) + public Result handleAppException(AppException e) { + return Result.error(e.getCode(), e.getMessage()); + } + +// @ExceptionHandler(Exception.class) +// public Result handleException(Exception e) { +// return Result.error(Constants.SYSTEM_ERROR, e.getMessage()); +// } + +} diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/mapper/UserMapper.java b/src/main/java/cn/edu/ecnu/stu/bookstore/mapper/UserMapper.java new file mode 100644 index 0000000..4aedea4 --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/mapper/UserMapper.java @@ -0,0 +1,28 @@ +package cn.edu.ecnu.stu.bookstore.mapper; + +import cn.edu.ecnu.stu.bookstore.pojo.User; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +@Mapper +public interface UserMapper { + + int insert(@Param("user") User user); + + int delete(@Param("userId") int userId); + + int checkUserByUsername(@Param("username") String username); + + int deleteByName(@Param("username") String username); + + @Select("select id, username, password, address, phone from t_user where username = #{username}") + User selectOneByName(@Param("username") String username); + + @Select("select count(*) from t_user where username = #{username} and password = #{password}") + int checkPassword(@Param("username") String username, @Param("password") String password); + + @Update("update t_user set password = #{password} where username = #{username}") + int updatePassword(@Param("username") String username, @Param("password") String password); +} diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/pojo/LoginUser.java b/src/main/java/cn/edu/ecnu/stu/bookstore/pojo/LoginUser.java new file mode 100644 index 0000000..9ef4fed --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/pojo/LoginUser.java @@ -0,0 +1,53 @@ +package cn.edu.ecnu.stu.bookstore.pojo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.io.Serializable; +import java.util.Collection; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LoginUser implements UserDetails { + + private User user; + + @Override + public Collection getAuthorities() { + return null; + } + + @Override + public String getPassword() { + return user.getPassword(); + } + + @Override + public String getUsername() { + return user.getUsername(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/pojo/User.java b/src/main/java/cn/edu/ecnu/stu/bookstore/pojo/User.java new file mode 100644 index 0000000..dcd45b1 --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/pojo/User.java @@ -0,0 +1,23 @@ +package cn.edu.ecnu.stu.bookstore.pojo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Data +public class User { + + private int id; + + private String username; + + private String password; + + private int balance; + + private String phone; + + private String address; +} diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/service/UserDetailsServiceImpl.java b/src/main/java/cn/edu/ecnu/stu/bookstore/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..cae8160 --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/service/UserDetailsServiceImpl.java @@ -0,0 +1,26 @@ +package cn.edu.ecnu.stu.bookstore.service; + +import cn.edu.ecnu.stu.bookstore.component.AppException; +import cn.edu.ecnu.stu.bookstore.component.Constants; +import cn.edu.ecnu.stu.bookstore.pojo.LoginUser; +import cn.edu.ecnu.stu.bookstore.pojo.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + + @Autowired + private UserService userService; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userService.selectUserByName(username); + if(user == null) + throw new AppException(Constants.CLIENT_ERROR, Constants.PARAMETER_ERROR_MESSAGE); + return new LoginUser(user); + } +} diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/service/UserService.java b/src/main/java/cn/edu/ecnu/stu/bookstore/service/UserService.java new file mode 100644 index 0000000..f2554cb --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/service/UserService.java @@ -0,0 +1,83 @@ +package cn.edu.ecnu.stu.bookstore.service; + +import cn.edu.ecnu.stu.bookstore.component.AppException; +import cn.edu.ecnu.stu.bookstore.component.Constants; +import cn.edu.ecnu.stu.bookstore.mapper.UserMapper; +import cn.edu.ecnu.stu.bookstore.pojo.LoginUser; +import cn.edu.ecnu.stu.bookstore.pojo.User; +import cn.edu.ecnu.stu.bookstore.utils.JwtUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.concurrent.TimeUnit; + +@Service +public class UserService { + + @Autowired + private UserMapper userMapper; + + @Autowired + private AuthenticationManager authenticationManager; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private RedisTemplate redisTemplate; + + public void register(User user) { + if(!StringUtils.hasLength(user.getUsername())) + throw new AppException(Constants.CLIENT_ERROR, Constants.PARAMETER_ERROR_MESSAGE); + if(userMapper.checkUserByUsername(user.getUsername()) != 0) + throw new AppException(Constants.CLIENT_ERROR, Constants.REGISTER_ERROR_MESSAGE); + String encoded = passwordEncoder.encode(user.getPassword()); + user.setPassword(encoded); + userMapper.insert(user); + } + + public void unregister(User user) { + User user1 = userMapper.selectOneByName(user.getUsername()); + if(user1 == null || !passwordEncoder.matches(user.getPassword(), user1.getPassword())) { + throw new AppException(Constants.CLIENT_ERROR, Constants.PASSWORD_ERROR); + } + userMapper.deleteByName(user.getUsername()); + } + + public User selectUserByName(String username) { + return userMapper.selectOneByName(username); + } + + public String login(User user) { + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()); + Authentication authenticate = authenticationManager.authenticate(token); + LoginUser loginUser = (LoginUser)authenticate.getPrincipal(); + redisTemplate.opsForValue().set("userId:" + loginUser.getUser().getId(), "1", 1, TimeUnit.DAYS); + return JwtUtil.getToken(loginUser.getUser()); + } + + public void logout(String username) { + User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + if(!user.getUsername().equals(username)) { + throw new AppException(Constants.CLIENT_ERROR, Constants.PARAMETER_ERROR_MESSAGE); + } + redisTemplate.delete("userId:" + user.getId()); + } + + public void changePassword(String username, String oldPassword, String newPassword) { + User user = userMapper.selectOneByName(username); + if(user == null || !passwordEncoder.matches(oldPassword, user.getPassword())){ + throw new AppException(Constants.CLIENT_ERROR, Constants.PASSWORD_ERROR); + } + newPassword = passwordEncoder.encode(newPassword); + userMapper.updatePassword(username, newPassword); + redisTemplate.delete("userId:" + user.getId()); + } +} diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/utils/JwtUtil.java b/src/main/java/cn/edu/ecnu/stu/bookstore/utils/JwtUtil.java new file mode 100644 index 0000000..369c37e --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/utils/JwtUtil.java @@ -0,0 +1,50 @@ +package cn.edu.ecnu.stu.bookstore.utils; + +import com.alibaba.fastjson.JSON; +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTCreator; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.Calendar; +import java.util.Date; +import java.util.Map; + +@Component +public class JwtUtil { + + /** 盐值*/ + private static final String SING="!@#%^$*$%#^$#%^%$$#QWBADasda881"; + + //生成令牌 + public static String getToken(Object object){ + //获取日历对象 + Calendar calendar= Calendar.getInstance(); + //默认1天过期 + calendar.add(Calendar.DATE,1); + //新建一个JWT的Builder对象 + JWTCreator.Builder builder = JWT.create(); + String jsonStr = JSON.toJSONString(object); + builder.withClaim("data",jsonStr); + //设置过期时间和签名 + return builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(SING)); + } + /** + * 验签并返回DecodedJWT + * @param token 令牌 + */ + public static Object getTokenInfo(String token,Class clazz){ + DecodedJWT jwt = JWT.require(Algorithm.HMAC256(SING)).build().verify(token); + return JSON.parseObject(jwt.getClaim("data").asString(), clazz); + } + + public static Date getTokenExpiration(String token) { + DecodedJWT jwt = JWT.require(Algorithm.HMAC256(SING)).build().verify(token); + return jwt.getExpiresAt(); + } + +} + diff --git a/src/main/java/cn/edu/ecnu/stu/bookstore/utils/RequestUtil.java b/src/main/java/cn/edu/ecnu/stu/bookstore/utils/RequestUtil.java new file mode 100644 index 0000000..b306a66 --- /dev/null +++ b/src/main/java/cn/edu/ecnu/stu/bookstore/utils/RequestUtil.java @@ -0,0 +1,103 @@ +package cn.edu.ecnu.stu.bookstore.utils; + +import cn.edu.ecnu.stu.bookstore.component.Result; +import com.alibaba.fastjson.JSON; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.util.EntityUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +public class RequestUtil { + + public static Result post(String url, Map bodyMap, String token) { + // post请求 + HttpClient httpClient; + HttpPost httpPost; + HttpResponse response; + String responseContent; + String body = bodyMap == null ? null : JSON.toJSONString(bodyMap); + try { + // 创建 httpClient + httpClient = HttpClients.createDefault(); + + httpPost = new HttpPost(url); + httpPost.addHeader("Accept", "*/*"); + httpPost.addHeader("Content-Type", "application/json;charset=utf8"); + if(token != null) + httpPost.addHeader("token", token); + + // set entity + if(body != null) + httpPost.setEntity(new StringEntity(body, StandardCharsets.UTF_8)); + + // 发送请求 + response = httpClient.execute(httpPost); + + // 得到响应 + HttpEntity httpEntity = response.getEntity(); + responseContent = EntityUtils.toString(httpEntity, "UTF-8"); + + // 释放资源 + EntityUtils.consume(httpEntity); + + return JSON.parseObject(responseContent, Result.class); + + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public static Result get(String url, Map params, String token) { + // post请求 + HttpClient httpClient; + HttpGet httpGet; + HttpResponse response; + String responseContent; + + try { + // 创建 httpClient + httpClient = HttpClients.createDefault(); + + httpGet = new HttpGet(url); + httpGet.addHeader("Accept", "*/*"); + httpGet.addHeader("Content-Type", "application/json;charset=utf8"); + if(token != null) + httpGet.addHeader("token", token); + + // set params + if(params != null) { + BasicHttpParams httpParams = new BasicHttpParams(); + for (Map.Entry entry : params.entrySet()) { + httpParams.setParameter(entry.getKey(), entry.getValue()); + } + httpGet.setParams(httpParams); + } + + // 发送请求 + response = httpClient.execute(httpGet); + + // 得到响应 + HttpEntity httpEntity = response.getEntity(); + responseContent = EntityUtils.toString(httpEntity, "UTF-8"); + + // 释放资源 + EntityUtils.consume(httpEntity); + + return JSON.parseObject(responseContent, Result.class); + + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..1d6cc5b --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,18 @@ +server: + port: 8080 + +mybatis: + mapper-locations: classpath:mapper/*.xml + type-aliases-package: cn.edu.ecnu.stu.bookstore.pojo + +spring: + datasource: + url: jdbc:mysql://localhost:3306/bookstore?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true + username: root + password: qwe030318 + driver-class-name: com.mysql.cj.jdbc.Driver + type: com.alibaba.druid.pool.DruidDataSource + redis: + host: 106.75.115.165 + port: 6379 + password: qwe030318 \ No newline at end of file diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000..1c579d5 --- /dev/null +++ b/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,22 @@ + + + + + insert into t_user(username, password, balance, address, phone) + values (#{user.username}, #{user.password}, #{user.balance}, #{user.address}, #{user.phone}) + + + + delete from t_user where id = #{userId} + + + + delete from t_user where username = #{username} + + + + \ No newline at end of file diff --git a/src/test/java/cn/edu/ecnu/stu/bookstore/BookstoreApplicationTests.java b/src/test/java/cn/edu/ecnu/stu/bookstore/BookstoreApplicationTests.java new file mode 100644 index 0000000..536cce4 --- /dev/null +++ b/src/test/java/cn/edu/ecnu/stu/bookstore/BookstoreApplicationTests.java @@ -0,0 +1,28 @@ +package cn.edu.ecnu.stu.bookstore; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; + +@SpringBootTest +class BookstoreApplicationTests { + + @Test + void contextLoads() { + } + + @Test + public void SetTest(@Autowired RedisTemplate redisTemplate){ + ValueOperations value = redisTemplate.opsForValue(); + value.set("springBoot","RedisOnSpringBoot"); + } + @Test + public void GetTest(@Autowired RedisTemplate redisTemplate){ + ValueOperations value = redisTemplate.opsForValue(); + Object o = value.get("springBoot"); + System.out.println(o); + } + +} diff --git a/src/test/java/cn/edu/ecnu/stu/bookstore/RegisterTest.java b/src/test/java/cn/edu/ecnu/stu/bookstore/RegisterTest.java new file mode 100644 index 0000000..333fd80 --- /dev/null +++ b/src/test/java/cn/edu/ecnu/stu/bookstore/RegisterTest.java @@ -0,0 +1,150 @@ +package cn.edu.ecnu.stu.bookstore; + +import cn.edu.ecnu.stu.bookstore.component.Constants; +import cn.edu.ecnu.stu.bookstore.component.Result; +import cn.edu.ecnu.stu.bookstore.utils.RequestUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpParams; +import org.apache.http.util.EntityUtils; +import org.apache.tomcat.util.http.ResponseUtil; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.UUID; + +@SpringBootTest +public class RegisterTest { + + @Test + public void testRegister() { + String url = Constants.URL_PREFIX + "/auth/register"; + Map map = new HashMap<>(); + String username = UUID.randomUUID().toString(); + String password = username + "x"; + map.put("username", username); + map.put("password", password); + Result result = RequestUtil.post(url, map, null); + assert result != null && result.getCode() != null && result.getCode().equals(Constants.SUCCESS); + + map.put("username", username); + result = RequestUtil.post(url, map, null); + assert result != null && result.getCode() != null && result.getCode().equals(Constants.CLIENT_ERROR); + } + + @Test + public void testLogin() { + String url = Constants.URL_PREFIX + "/auth/register"; + Map map = new HashMap<>(); + String username = UUID.randomUUID().toString(); + String password = username + "x"; + map.put("username", username); + map.put("password", password); + Result result = RequestUtil.post(url, map, null); + + assert result != null && result.getCode() != null && result.getCode().equals(Constants.SUCCESS); + + url = Constants.URL_PREFIX + "/auth/login"; + result = RequestUtil.post(url, map, null); + assert result != null && result.getCode() != null && result.getCode().equals(Constants.SUCCESS); + + map.put("password", password + "x"); + result = RequestUtil.post(url, map, null); + assert result != null && result.getCode() != null && result.getCode().equals(Constants.SYSTEM_ERROR); + } + + @Test + public void testUnregister() { + String registerUrl = Constants.URL_PREFIX + "/auth/register"; + Map map = new HashMap<>(); + String username = UUID.randomUUID().toString(); + String password = username + "x"; + map.put("username", username); + map.put("password", password); + Result result = RequestUtil.post(registerUrl, map, null); + + assert result != null && result.getCode() != null && result.getCode().equals(Constants.SUCCESS); + + String loginUrl = Constants.URL_PREFIX + "/auth/login"; + result = RequestUtil.post(loginUrl, map, null); + assert result != null && result.getCode() != null && result.getCode().equals(Constants.SUCCESS); + + String unregisterUrl = Constants.URL_PREFIX + "/auth/unregister"; + result = RequestUtil.post(unregisterUrl, map, null); + assert result != null && result.getCode() != null && result.getCode().equals(Constants.SUCCESS); + + result = RequestUtil.post(loginUrl, map, null); + assert result != null && result.getCode() != null && result.getCode().equals(Constants.CLIENT_ERROR); + } + + @Test + public void testLogout() { + String registerUrl = Constants.URL_PREFIX + "/auth/register"; + Map map = new HashMap<>(); + String username = UUID.randomUUID().toString(); + String password = username + "x"; + map.put("username", username); + map.put("password", password); + Result result = RequestUtil.post(registerUrl, map, null); + + assert result != null && result.getCode() != null && result.getCode().equals(Constants.SUCCESS); + + String loginUrl = Constants.URL_PREFIX + "/auth/login"; + result = RequestUtil.post(loginUrl, map, null); + assert result != null && result.getCode() != null && result.getCode().equals(Constants.SUCCESS); + + JSONObject data = (JSONObject) result.getData(); + String token = data.getString("token"); + String testUrl = Constants.URL_PREFIX + "/test"; + result = RequestUtil.get(testUrl, null, token); + assert result != null && result.getCode() != null && result.getCode().equals(Constants.SUCCESS); + + String logoutUrl = Constants.URL_PREFIX + "/auth/logout"; + result = RequestUtil.post(logoutUrl, map, token); + assert result != null && result.getCode() != null && result.getCode().equals(Constants.SUCCESS); + + result = RequestUtil.get(testUrl, null, token); + assert result != null && result.getCode() != null && result.getCode().equals(Constants.AUTHENTICATION_ERROR); + } + + @Test + public void testPassword() { + String registerUrl = Constants.URL_PREFIX + "/auth/register"; + Map map = new HashMap<>(); + String username = UUID.randomUUID().toString(); + String password = username + "x"; + map.put("username", username); + map.put("password", password); + Result result = RequestUtil.post(registerUrl, map, null); + + assert result != null && result.getCode() != null && result.getCode().equals(Constants.SUCCESS); + + String newPassword = password + "x"; + map.put("oldPassword", password); + map.put("newPassword", newPassword); + String passwordUrl = Constants.URL_PREFIX + "/auth/password"; + result = RequestUtil.post(passwordUrl, map, null); + assert result != null && result.getCode() != null && result.getCode().equals(Constants.SUCCESS); + + map.put("password", newPassword); + String loginUrl = Constants.URL_PREFIX + "/auth/login"; + result = RequestUtil.post(loginUrl, map, null); + assert result != null && result.getCode() != null && result.getCode().equals(Constants.SUCCESS); + } + +} +