kob后端1
kob后端
1.四个层;
SpringBoot中的常用模块
pojo层:将数据库中的表对应成Java中的Class
mapper层(也叫Dao层):将pojo层的class中的操作,映射成sql语句
service层:写具体的业务逻辑,组合使用mapper中的操作
controller层:负责请求转发,接受页面过来的参数,传给Service处理,接到返回值,再传给页面
mapper层
mybatis-plus帮我们实现了一些内容,我们不用自己手写sql语句:需要让UserMapper接口继承BaseMapper
queryWrapper数据库动态查询条件;queryWrapper
QueryWrapper实例
/**
* 案例三:年龄范围查询(20-30之间的)
* @return
*/
@RequestMapping("/list3")
public Map<String,Object> getList3(){
Map<String,Object> result = new HashMap<>();
//构建一个查询的wrapper
QueryWrapper<User> wrapper = new QueryWrapper<User>();
//年龄20-30之间的
wrapper.between("age",20,30);
//未删除
wrapper.eq("del_flag",0);
//创建时间降序
wrapper.orderByDesc("create_time");
List<User> list = userMapper.selectList(wrapper);
result.put("data",list);
return result;
}
不会用默认的Username去查询,而是会根据用户名去database中查找用户名和密码,然后再判断用户名和密码是否匹配;
报错Encoding…是因为密码没有加密,如果想要浏览器密码未加密,需要在密码前加上{noop},表示密码是明文未加密:
实现密码加密:
方式1:session验证
实现config.SecurityConfig类,用来实现用户密码的加密存储
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
SecurityConfig对象一定会调用passwordEncoder()方法,返回使用的那种加密方法:
几个核心API:
encode将明文转换成密文;
matches:判断明文和密文是否匹配;
方式2:JWT(目前使用较多)验证,对应前后端分离(因为前后端分离存在跨域问题)
获取到一个url之后,分为两种页面,一种是类似login页面,只要输入url就可以访问,另一种比如根页面,必须经过授权之后才能访问,否则当访问该页面的时候可能会跳转到其他页面比如Login页面:
第一种session验证方式,用户输入用户名密码提交之后,会先根据用户名到用户名进行查找,找到对应的username和password之后,才将其和用户输入的username_input,password_input进行比对,如果比对成功,则通过了验证,此时会给用户发送一个sessionID,用户收到sessionID之后会将sessionID 存到自己的本地(Cookie),未来每次在访问网站的时候,都会自动的将浏览器(cookie)存储的sessionID发送给服务器;前边服务器在将sessionID发送给用户的时候,自己也会存储一份;sessionID对用户来说相对于一个令牌,用户后续在访问页面的时候,会将sessionID令牌带上,服务器在接收到用户想要访问授权页面的请求的时候,会将sessionID从用户发送的请求中提取出来,判断sessionID是否有效,服务器不仅会存储sessionID,实际上是存储了一个sessionID-user的映射,若判断访问服务器的这个链接是有效的话,那么就从服务端将该sessionID对应的用户信息提取出来,提取到上下文中(每一个url都会对应一个controller,通过controller中的某个接口将这个用户提取出来),之后就可以正常访问controller逻辑了;
jwt验证的优势:
1.实现了跨域;
2.不需要在服务端上存储;(不需要将令牌存储到多个服务器上,有一个Jwt令牌就可以访问多个服务器)
jwt验证原理:
同理,用户登陆验证通过之后,用户会得一个jwt_token,而服务器给用户发了一个jwt_token之后,自己是不需要存储任何信息的;
那么,如何判断这个用户是哪个用户呢???
将用户的一些信息加入到jwt_token中,如图所示
用户的user_id+服务端生成的秘钥,通过某种方式进行加密,得到加密后的字符串,再将user_id和加密后的字符串进行拼接,得到了jwt_token,
服务端如何验证呢?
同样获取到user_id,同理给user_id先拼接上秘钥,然后根据固定的加密方法(同上述步骤一样),判断用户传过来的jwt_token和根据用户传过来的user_id现生成的jwt_token是不是一样的;
防止他人窃取jwt_token
一般而言,jwt_token是分为access_token和refresh_token传过来的,access_token时间较短,比如5min,而refresh_token时间较长,
同时,用户的请求有Get(明文,不安全)方式的,也有Post(密文,安全)的,用户在请求的时候会优先使用短时间的令牌,如果短时间的令牌过期了,会重新使用post请求根据refresh_token令牌重新得到一个新的access_token令牌,
Controller
GetMapping注解
如GetMapping(“/user/all/”)
给主键id添加自增注解(@TableId(type= IdType.AUTO)
后端API
1.实现/user/account/token/:验证用户名密码,验证成功后返回jwt token(令牌)
2.实现/user/account/info/:根据令牌返回用户信息
3.+实现/user/account/register/:注册账号
1.在service中写API的接口;
2.在service的impl下写对应API的实现;
3.字controller中写service的调用;
如图创建如下用户目录,在service目录下创建/user/account/,下写API的接口,在impl下创建相同目录/user/account/下写对应API的实现,
@Service
public class LoginServiceImpl implements LoginService {
@Override
public Map<String, String> login(String username, String password) {
return null;
}
}
在实现类前要加上Service注解;
一些常用注解的解释
Bean层:
@Autowired:自动导入依赖的bean;
Service层:
@Service:一般用于修饰service层的组件,用于service实现类中;
Controller层
@Controller:用于定义控制器类,在spring项目中由控制器负责将用户发来的URL请求转发到对应的服务接口(service层),一般这个注解在类中,通常方法需要配合注解@RequestMapping。用于定义控制器类
@RequestMapping:提供路由信息,负责URL到Controller中的具体函数的映射。
@GetMapping(链接地址):用于将HTTPGET请求映射到特定处理程序方法的注释。具体来说,@GetMapping是一个作为快捷方式的组合注释@RequestMapping(method = RequestMethod.GET)。
@PostMapping(链接地址),是@RequestMapping(method = RequestMethod.POST)的缩写用于将HTTP POST请求映射到特定处理程序方法的注释。
@RestController:用于标注控制层组件(如struts中的action),@ResponseBody和@Controller的合集。
在实现这三个后端API之前,需要先修改Spring Security(Spring Security 是 Spring 家族中的一个安全管理框架)
1.实现service.impl.UserDetailsServiceImpl类,继承自UserDetailsService接口,用来接入数据库信息
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;//使用任何bean之前,需要先注入
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//设置好动态查询条件,通过userMapper在数据库中根据用户名进行查找,如果查找结果不为空,返回用户详细信息对象;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
User user = userMapper.selectOne(queryWrapper);
if (user == null) {
throw new RuntimeException("User Does not Exist!");
}
return new UserDetailsImpl(user);
}
}
//用户详细信息UserDetails接口的实现类
package com.kob.backend.service.impl.utils;
import com.kob.backend.pojo.User;
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//三个注解表示自动为该类添加get,set,有参构造,无参构造等基本方法
public class UserDetailsImpl implements UserDetails {
private User user;//这个后添加的,其余全部是实现的接口函数
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return user.getPassword();//这里改成获取密码,原本是null
}
@Override
public String getUsername() {
return user.getUsername();//这里改成获取用户名,原本是null
}
@Override
public boolean isAccountNonExpired() {
return true;//改成true,账户为过期?
}
@Override
public boolean isAccountNonLocked() {
return true;//改成true,账户未锁定?
}
@Override
public boolean isCredentialsNonExpired() {
return true;//改成true,凭证未过期?
}
@Override
public boolean isEnabled() {
return true;//改成true,是否启用?
}
}
2.实现config.SecurityConfig类,用来实现用户密码的加密存储
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3.实现utils.JwtUtil类,为jwt工具类,用来创建、解析jwt token
package com.kob.backend.service.impl.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; // 有效期14天单位时Ms
public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds1fsfeseasfesfe21232131afasdfac";//设置再服务端的字符串
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();
}
}
4.实现config.filter.JwtAuthenticationTokenFilter类,用来验证jwt token,如果验证成功,则将User信息注入上下文中
package com.kob.backend.config.filter;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.service.impl.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);
}
}
5.配置config.SecurityConfig类,放行登录、注册等接口
package com.kob.backend.config;
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);
}
}
后端API实现1.实现/user/account/token/:验证用户名密码,验证成功后返回jwt token(令牌)
创建LoginService接口,并写出实现类
package com.kob.backend.service.impl.user.account;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.JwtUtil;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.service.user.account.LoginService;
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;//authenticationManager(身份验证管理器);
@Override
public Map<String, String> getToken(String username, String password) {
//将username和passwd封装成这样的对象之后,就不会存明文了;
UsernamePasswordAuthenticationToken authenticationToken=new
UsernamePasswordAuthenticationToken(username,password);//UsernamePasswordAuthenticationToken (用户名密码认证令牌)
//进行验证;登陆失败会自动处理
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//登陆成功的话将用户名和密码取出;
UserDetailsImpl loginUser=(UserDetailsImpl)authenticate.getPrincipal();
User user=loginUser.getUser();
//通过用户的userid生成jwt令牌
String jwt= JwtUtil.createJWT(user.getId().toString());
Map<String,String> map=new HashMap<>();
map.put("err_message","success");//不需要处理失败的情况,因为登陆失败的话,Authentication会自动抛出异常,不会执行到这里;
map.put("token",jwt);
return map;
}
}
写出对应的调用controller
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.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/")//登陆通常使用post请求,因为get请求会将用户名和密码暴露在url中,
括号内为链接地址,可以随意定义,注意该链接需要放行:在SecurityConfig中的configure方法中将该链接添加上;
public Map<String,String> getToken(@RequestParam Map<String,String> map){//从post请求中将参数拿出来,需加上注解,将post请求中的参数放到一个map中
//这里的RequestParam注解后需要的参数是从前端发送的post请求中提取出来的;
String username=map.get("username");
String password=map.get("password");
return loginService.getToken(username,password);
}
}
当前端发送url地址/user/account/token/的Posting请求时,执行对应该链接的方法getToken,从前端发送过来的请求中获取到username,和password,返回该username,password(如果验证通过)的jwt令牌
后端API实现2.实现/user/account/info/根据令牌获取用户信息
InfoServiceImpl:
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.security.core.userdetails.UserDetails;
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 authenticationToken=
(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
//将当前的登陆用户取出;
UserDetailsImpl loginUser=(UserDetailsImpl)authenticationToken.getPrincipal();
User user=loginUser.getUser();
Map<String,String> map=new HashMap<>();
map.put("err_message","success");
map.put("id",user.getId().toString());
map.put("username",user.getUsername());
map.put("password",user.getPassword());
map.put("photo",user.getPhoto());
return map;//返回获取到的用户信息;e
}
}
InfoServiceController类
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 InfoServiceController {
@Autowired
private InfoService infoService;
@GetMapping("/user/account/info/")
public Map<String,String> getInfo(){
return infoService.getinfo();
}
}
3.实现/user/account/register/:注册账号
实现RegisterServiceImpl类:
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
UserMapper userMapper;
@Autowired
PasswordEncoder passwordEncoder;//给密码加密的工具类
@Override
public Map<String, String> register(String username, String password, String confirmedpasswd) {
Map<String, String> map = new HashMap<>();
if (username == null) {
map.put("err_message", "用户名不能为空");
return map;
}
if (password == null || confirmedpasswd == null) {
map.put("err_message", "密码不能为空");
return map;
}
username = username.trim();
if (username.length() == 0) {
map.put("err_message", "用户名不能为空");
return map;
}
if (password.length() == 0 || confirmedpasswd.length() == 0) {
map.put("error_message", "密码不能为空");
}
if (username.length() > 100) {
map.put("error_message", "用户名长度不能大于100");
return map;
}
if (password.length() > 100 || confirmedpasswd.length() > 100) {
map.put("error_message", "密码长度不能大于100");
return map;
}
if (!password.equals(confirmedpasswd)) {
map.put("error_message", "两次输入的密码不一致");
return map;
}
QueryWrapper<User> queryWrapper=new QueryWrapper<User>();
queryWrapper.eq("username",username);
List<User> userList=userMapper.selectList(queryWrapper);
if(userList!=null){
map.put("error_message", "用户名已存在");
return map;
}
String encodedPassword=passwordEncoder.encode(password);
String photo="https://cdn.acwing.com/media/user/profile/photo/180265_lg_5dc3115a2f.png";//设置一个默认头像;
User user=new User(null,username,encodedPassword,photo);//id设置为null即可,自增;
userMapper.insert(user);
map.put("error_message", "success");
return map;
}
}
实现controller层:
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 RegisterServiceController {
@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 confirmedPasswd=map.get("confirmedPassword");
return registerService.register(username,password,confirmedPasswd);
}
}
至此,就实现了登陆,获取用户信息,注册三种功能;
验证的时候可以在打开一个页面,在setup()中使用ajax向指定的url发送数据,之后相应的数据会显示在控制台;
setup() {
$.ajax({
url: 'http://localhost:3000/user/account/token/',
type: "post",
data: {
username: "zhr",
password: "123456",
},
success(resp) {
console.log(resp);
},
error(resp) {
console.log(resp);
}
});
$.ajax({
url: "http://127.0.0.1:3000/user/account/register/",
type: "post",
data: {
username: "zhrr",
password: "114514",
confirmedPassword: "114514",
},
success(resp) {
console.log(resp);
},
error(resp) {
console.log(resp);
}
})
}