Spring操作数据库的方式
pojo层
:将数据库中的表table
对应成Java
中的Class
mapper层
(也叫Dao层):将pojo
层的class
中的增删改查操作,映射成sql
语句。一个表会对应一个pojo
层与一个mapper
层
service层
:写具体的业务逻辑,组合使用mapper
中的操作。mapper
类似于一些最基本的操作, service
可以将最基本的操作组合实现一些具体的业务
controller层
:将前端的请求以及请求中的参数接收然后选择将这些参数传给哪个service
,接到返回值,再传给页面
Pojo层
主要通过Lombok
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.kob.backend.pojo;
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;
@Data @NoArgsConstructor @AllArgsConstructor public class User { private Integer id; private String username; private String password; }
|
@Data
:自动添加常用函数
@NoArgsConstructor
:自动添加无参构造函数
@AllArgsConstructor
:自动添加构造函数
Mapper层
1 2 3 4 5 6 7 8 9
| package com.kob.backend.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.kob.backend.pojo.User; import org.apache.ibatis.annotations.Mapper;
@Mapper public interface UserMapper extends BaseMapper<User> { }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| package com.kob.backend.controller.user;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.kob.backend.mapper.UserMapper; import com.kob.backend.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController public class UserController {
@Autowired UserMapper userMapper;
@GetMapping("/user/all/") public List<User> getAll() { return userMapper.selectList(null); }
@GetMapping("/user/{userId}") public User getUser(@PathVariable int userId) { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("id", userId); return userMapper.selectOne(queryWrapper); }
@GetMapping("/user/add/{userId}/{username}/{password}") public String addUser( @PathVariable int userId, @PathVariable String username, @PathVariable String password) { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String encodedPassword = passwordEncoder.encode(password); User user = new User(userId, username, encodedPassword); userMapper.insert(user); return "Add User Successfully"; }
@GetMapping("/user/delete/{userId}") public String deleteUser(@PathVariable int userId) { userMapper.deleteById(userId); return "Delete User Successfully"; } }
|
Spring Security
数据库会知道session id
对应的用户,所以如果想让spring security对接数据库需要把username在数据库中对应的用户找出来,返回他的密码,需要用到数据库操作mapper。
@Autowired
:操作数据库内数据
PasswordEncoder
UserDetailsServiceImpl
实现service.impl.UserDetailsServiceImpl
类,继承自UserDetailsService
接口,用来接入数据库信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.kob.backend.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.kob.backend.mapper.UserMapper; import com.kob.backend.pojo.User; import com.kob.backend.service.impl.utils.UserDetailsimpl; 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 UserMapper userMapper;
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("username", username); User user = userMapper.selectOne(queryWrapper); if (user ==null) { throw new RuntimeException("用户不存在!"); }
return new UserDetailsimpl(user); } }
|
UserDetailsimpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package com.kob.backend.service.impl.utils;
import com.kob.backend.pojo.User; import com.kob.backend.service.impl.UserDetailsServiceImpl; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
@Data @NoArgsConstructor @AllArgsConstructor public class UserDetailsimpl implements UserDetails {
private User user;
@Override public Collection<? extends GrantedAuthority> 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; } }
|
SecurityConfig
实现config.SecurityConfig
类,用来实现用户密码的加密存储:
1 2 3 4 5 6 7 8 9
| @Configuration @EnableWebSecurity public class SecurityConfig {
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
|
权限管理
一般分为两种权限, 一种是公开页面,所有人都可以访问;另一种是授权页面,即只有登录了才可以访问
基于session的认证方式
它的交互流程是,用户认证成功后,在服务端将用户信息保存在session(当前会话)中,发给客户端的sesssion_id存放到cookie中,这样用户客户端请求时带上session_id就可以验证服务器端是否存在 session数据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了。
基于token的认证方式
它的交互流程是,用户认证成功后,服务端生成一个token(令牌,一个字符串标识)发给客户端,客户端可以放到cookie或 localStorage等存储中,每次请求时带上token,服务端收到token通过验证后即可确认用户身份。这种方式主要用于分布式系统中,将token和用户信息存储在Redis中,实现会话共享。
session ID
用户输入用户名和密码,然后通过UserDetailsServiceImpl
类从数据库里获取用户信息并进行比对,如果二者一致,则会发送一个session ID
,用于身份验证,用户将这个ID存到本地(cookies)里,每次访问网站就会将存储的ID发送到服务器,服务器也会自己存一份ID与ID对应的用户用于比对。
未来用户在访问授权页面时会将这个ID发送到服务器,服务器端会从访问的请求中提取出这个ID并判断是否有效,若有效将用户信息提取到上下文中,即可以在Controller中通过某个接口访问用户信息。
jwt
在用户登陆成功后,不给予session ID
而是换成一个jwt-token
。服务器不需要存储token信息,而是把userId
等信息加入到token里。比如第一段存储用户信息,第二段存储服务器端的密钥,然后通过加密算法将用户信息+密钥进行加密得到加密后的字符串,并把这用户信息+加密后的字符串拼起来返回给用户,这一段拼接也被称为jwt-token
。
在申请访问时,用户会将这个token发送到服务器端,服务器端会先验证令牌是否合法,若合法则通过token中的userid查找用户,并进行授权
jwt实现
依赖
- jjwt-api
- jjwt-impl
- jjwt-jackson
utils.JwtUtil类
jwt工具类,用来创建、解析jwt token
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| package com.kob.backend.utils;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.stereotype.Component;
import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; import java.util.Date; import java.util.UUID;
@Component public class JwtUtil { public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14; public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";
public static String getUUID() { return UUID.randomUUID().toString().replaceAll("-", ""); }
public static String createJWT(String subject) { JwtBuilder builder = getJwtBuilder(subject, null, getUUID()); return builder.compact(); }
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; SecretKey secretKey = generalKey(); long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); if (ttlMillis == null) { ttlMillis = JwtUtil.JWT_TTL; }
long expMillis = nowMillis + ttlMillis; Date expDate = new Date(expMillis); return Jwts.builder() .setId(uuid) .setSubject(subject) .setIssuer("sg") .setIssuedAt(now) .signWith(signatureAlgorithm, secretKey) .setExpiration(expDate); }
public static SecretKey generalKey() { byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY); return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256"); }
public static Claims parseJWT(String jwt) throws Exception { SecretKey secretKey = generalKey(); return Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(jwt) .getBody(); } }
|
config.filter.JwtAuthenticationTokenFilter类
用来验证jwt token,如果验证成功,则将User信息注入上下文中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| import com.kob.backend.mapper.UserMapper; import com.kob.backend.pojo.User; import com.kob.backend.service.impl.utils.UserDetailsImpl; import com.kob.backend.utils.JwtUtil; import io.jsonwebtoken.Claims; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; 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 JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private UserMapper userMapper;
@Override protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException { String token = request.getHeader("Authorization");
if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) { filterChain.doFilter(request, response); return; }
token = token.substring(7);
String userid; try { Claims claims = JwtUtil.parseJWT(token); userid = claims.getSubject(); } catch (Exception e) { throw new RuntimeException(e); }
User user = userMapper.selectById(Integer.parseInt(userid));
if (user == null) { throw new RuntimeException("用户名未登录"); }
UserDetailsImpl loginUser = new UserDetailsImpl(user); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response); } }
|
config.SecurityConfig类
放行登录、注册等接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| import com.kob.backend.config.filter.JwtAuthenticationTokenFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; 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; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
@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("/user/account/token/", "/user/account/register/").permitAll() .antMatchers(HttpMethod.OPTIONS).permitAll() .anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } }
|
在IDEA数据库可视化页面中勾选id升序需要在pojo页面修改代码,添加@Table(type = IdType.Auto)
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.kob.backend.pojo;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;
@Data @NoArgsConstructor @AllArgsConstructor public class User { @TableId(type = IdType.AUTO) private Integer id; private String username; private String password; private String photo; }
|
api
api共需要添加三个地方:
Controller
用来调用Service
Service
中需要一个接口
- Service中的
impl
实现一个接口
创建接口
service.user.account
下共创建三个接口:RegisterService
、InfoService
、LoginService
,分别对应注册、用户信息、登录三个功能。
Login
实现方法
LoginServiceImpl
在service.impl.user.account
下创建LoginServiceImpl
。
需要验证用户是否登陆成功,使用AuthenticationManager
,如果登陆失败则自动处理,若登陆成功则取出用户信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package com.kob.backend.service.impl.user.account;
import com.kob.backend.pojo.User; import com.kob.backend.service.impl.utils.UserDetailsImpl; import com.kob.backend.service.user.account.LoginService; import com.kob.backend.utils.JwtUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service;
import java.util.HashMap; import java.util.Map;
@Service public class LoginServiceImpl implements LoginService {
@Autowired private AuthenticationManager authenticationManager;
@Override public Map<String, String> login(String username, String password) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
Authentication authenticate = authenticationManager.authenticate(authenticationToken); UserDetailsImpl loginUser = (UserDetailsImpl) authenticate.getPrincipal(); User user = loginUser.getUser(); String jwt = JwtUtil.createJWT(user.getId().toString());
Map<String, String> map = new HashMap<>(); map.put("error_message", "success"); map.put("token", jwt);
return map; } }
|
调用方法
在controller.user.account
下创建LoginController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.kob.backend.controller.user.account;
import com.kob.backend.service.user.account.LoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController public class LoginController { @Autowired private LoginService loginService;
@PostMapping("/user/account/token") public Map<String, String> getToken(@RequestParam Map<String, String> map) { String username = map.get("username"); String password = map.get("password");
return loginService.getToken(username, password); } }
|
Info
实现方法
在service.impl.user.account
下创建InfoServiceImpl
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package com.kob.backend.service.impl.user.account;
import com.kob.backend.pojo.User; import com.kob.backend.service.impl.utils.UserDetailsImpl; import com.kob.backend.service.user.account.InfoService; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service;
import java.util.HashMap; import java.util.Map;
@Service public class InfoServiceImpl implements InfoService { @Override public Map<String, String> getinfo() { UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl loginUser = (UserDetailsImpl) authentication.getPrincipal(); User user = loginUser.getUser();
Map<String, String> map = new HashMap<>(); map.put("error_message", "success"); map.put("id", user.getId().toString()); map.put("username", user.getUsername()); map.put("photo", user.getPhoto());
return map; } }
|
调用方法
在controller.user.account
下创建InfoController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.kob.backend.controller.user.account;
import com.kob.backend.service.user.account.InfoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController public class InfoController { @Autowired private InfoService InfoService;
@GetMapping("/user/account/info") public Map<String, String> getInfo() { return InfoService.getinfo(); } }
|
Register
实现方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| package com.kob.backend.service.impl.user.account;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.kob.backend.mapper.UserMapper; import com.kob.backend.pojo.User; import com.kob.backend.service.user.account.RegisterService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service;
import java.util.HashMap; import java.util.List; import java.util.Map;
@Service public class RegisterServiceImpl implements RegisterService {
@Autowired private UserMapper userMapper;
@Autowired private PasswordEncoder passwordEncoder;
@Override public Map<String, String> register(String username, String password, String comfirmedpassword) {
Map<String, String> map = new HashMap<>(); if (username == null) { map.put("error_message", "用户名不能为空"); return map; } if (password == null || comfirmedpassword == null) { map.put("error_message", "密码不能为空"); return map; }
username = username.trim(); if (username.length() == 0) { map.put("error_message", "用户名不能为空"); return map; }
if (password.length() == 0 || comfirmedpassword.length() == 0) { map.put("error_message", "密码不能为空"); return map; }
if (username.length() > 100) { map.put("error_message", "用户名长度不能大于100"); return map; }
if (password.length() > 100 || comfirmedpassword.length() > 100) { map.put("error_message", "密码长度不能大于100"); return map; }
if (!password.equals(comfirmedpassword)) { map.put("error_message", "两次输入的密码不一致"); return map; }
QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("username", username); List<User> users = userMapper.selectList(queryWrapper); if (!users.isEmpty()) { map.put("error_message", "用户名已存在"); return map; }
String encodedPassword = passwordEncoder.encode(password); String photo = "https://ep13.top/images/head.jpg"; User user = new User(null, username, encodedPassword, photo); userMapper.insert(user);
map.put("error_message", "success"); return map; } }
|
调用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.kob.backend.controller.user.account;
import com.kob.backend.service.user.account.RegisterService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController public class RegisterController { @Autowired private RegisterService registerService;
@PostMapping("/user/account/register/") public Map<String, String> register(@RequestParam Map<String, String> map) { String username = map.get("username"); String password = map.get("password"); String confirmedPassword = map.get("confirmedPassword"); return registerService.register(username, password, confirmedPassword); } }
|