SpringBoot第六篇-SpringBoot+Mybatis+SpringSecurity+JWT整合,开发工具IDea
一、新建SpringBoot项目
,选择Maven,插件选择SpringWeb
二、引入springSecurity包
1 2 3 4 | <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> |
三、设置启动配置类
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 | //AOP : 拦截器 @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { //链式编程 @Override protected void configure(HttpSecurity http) throws Exception { // super.configure(http); //首页所有人可以访问,功能页只有对应权限的人才能访问 //请求授权的规则 http.authorizeRequests() .antMatchers( "/logina/**" ).permitAll() .antMatchers( "/hello/**" ).hasRole( "vip1" ) ; //vip3用户只可以访问该路径下的页面 //没有权限默认回到登录页面,需要开启登录的页面 http.formLogin(); //注销,开启了注销功能,跳到首页 //http.logout().logoutSuccessUrl("/"); //定制登录页 http.formLogin(); //开启记住我功能 cooke http.rememberMe(); } //认证 springboot 2.1.X 可以直接使用 //密码编码: passwordEncoder //在spring Security 5.0+ 新增了很多的加密方法 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //这些数据正常应该从数据库中读取 现在测试的是从内存中读取的数据 auth.inMemoryAuthentication().passwordEncoder( new BCryptPasswordEncoder()) //加了一个密码的编码规则 .withUser( "LJ" ).password( new BCryptPasswordEncoder().encode( "123" )).roles( "vip2" , "vip3" ) .and() .withUser( "root" ).password( new BCryptPasswordEncoder().encode( "123" )).roles( "vip1" , "vip2" , "vip3" ) .and() .withUser( "guest" ).password( new BCryptPasswordEncoder().encode( "123" )).roles( "vip1" ); //从数据库中获取数据 // auth.jdbcAuthentication() // .dataSource(dataSource) // .withDefaultSchema() // .withUser(users.username("user").password("password").roles("USER")) // .withUser(users.username("admin").password("password").roles("USER","ADMIN")); } |
四、创建控制器
@RestController,@RequestMapping,@GetMapping ,@Controller,@responbody的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @RestController @RequestMapping( "/logina" ) public class LoginController { @GetMapping( "/getlogin" ) public String getLogin(){ return "登录" ; } @GetMapping( "/getlogin2" ) public String getLogin2(){ return "登录" ; } } |
用户名:user,密码:
输入以后登陆成功跳转到
4、创建数据库表SysUser表,里面包含ID,username,password,rolename字段
5、创建实体,放到entities包下面
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 | package cn.yinmingneng.yinmingneng.entities; public class SysUser { private Integer id; public Integer getId() { return id; } public void setId(Integer id) { this .id = id; } public String getUsername() { return username; } public void setUsername(String username) { this .username = username; } public String getPassword() { return password; } public void setPassword(String password) { this .password = password; } public String getRolename() { return rolename; } public void setRolename(String rolename) { this .rolename = rolename; } private String username; private String password; private String rolename; } |
6、创建usermapper对象,用于自动生成sysuser表的增删改查语句,此类是个接口,通过java动态代理生成,我使用配置文件生成增删改查语句
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 | @Mapper public interface SysUserMapper { /** * 添加用户 * @param sysUser * @return */ int insertSysUser(SysUser sysUser); /** * 更新 * @param sysUser * @return */ int updateSysUser(SysUser sysUser); /** * 删除用户 * @return */ int deleteSysUserById(Integer id); SysUser getAll(String userName); } |
7、编写映射的SysUserMapper.XML文件,id和接口的方法名一样,否则会报错,映射不到
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 | <?xml version= "1.0" encoding= "UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace= "cn.yinmingneng.yinmingneng.mapper.SysUserMapper" > <sql id= "selectSysUser" > SELECT id ,username,password,rolename FROM sysuser </sql> <select id= "getAll" parameterType= "String" resultType= "SysUser" > SELECT id ,username,password,rolename FROM sysuser where username = #{username} </select> <insert id= "insertSysUser" parameterType= "SysUser" > insert into sysuser ( < if test= "username != null and username != '' " >username,</ if > < if test= "password != null and password != '' " >password,</ if > < if test= "rolename != null and rolename != '' " >config_value,</ if > )values( < if test= "username != null and username != ''" >#{username},</ if > < if test= "password != null and password != ''" >#{password},</ if > < if test= "rolename != null and rolename != ''" >#{rolename},</ if > ) </insert> <update id= "updateSysUser" parameterType= "SysUser" > update sysuser <set> < if test= "username != null and username != ''" >username = #{username},</ if > < if test= "password != null and password != ''" >password = #{password},</ if > < if test= "rolename != null and rolename != ''" >rolename = #{rolename}</ if > </set> where id = #{id} </update> <delete id= "deleteSysUserById" parameterType= "Integer" > delete from sysuser where id = #{id} </delete> </mapper> |
8、服务层SysUserService代码,接口,方便接口化编程
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 | package cn.yinmingneng.yinmingneng.Services; import cn.yinmingneng.yinmingneng.entities.SysUser; import org.springframework.stereotype.Service; import java.util.List; public interface SysUserService { /** * 添加用户 * @param sysUser * @return */ public int insertSysUser(SysUser sysUser); /** * 更新 * @param sysUser * @return */ public int updateSysUser(SysUser sysUser); /** * 删除用户 * @return */ public int deleteSysUserById(); public SysUser getAll(String userName); } |
9、服务层实现类,SysUserServiceImpl
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 | package cn.yinmingneng.yinmingneng.Services.ServiceIml; import cn.yinmingneng.yinmingneng.Services.SysUserService; import cn.yinmingneng.yinmingneng.entities.SysUser; import cn.yinmingneng.yinmingneng.mapper.SysUserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class SysUserServiceImpl implements SysUserService { @Autowired SysUserMapper sysUserMapper; @Override public int insertSysUser(SysUser sysUser) { return sysUserMapper.insertSysUser(sysUser); } @Override public int updateSysUser(SysUser sysUser) { return sysUserMapper.updateSysUser(sysUser); } @Override public int deleteSysUserById() { return 0 ; } @Override public SysUser getAll(String userName) { return sysUserMapper.getAll(userName); } } |
10、控制器测试层,永远测试带权限功能,不带权限功能,是否登录功能,匿名访问功能
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 | package cn.yinmingneng.yinmingneng.Controller; import cn.yinmingneng.yinmingneng.Services.SysUserService; import cn.yinmingneng.yinmingneng.entities.SysUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class TestController { @Autowired SysUserService sysUserService; @RequestMapping ( "/gethello" ) public String getHello(){ return "你好!" ; } /** * 添加用户 * @param sysUser * @return */ @RequestMapping ( "/insertsysuser" ) public int insertSysUser(SysUser sysUser) { return sysUserService.insertSysUser(sysUser); } /** * 更新 * @param sysUser * @return */ @RequestMapping ( "/a/updatesysuser" ) @PreAuthorize ( "hasRole('ROLE_USER')" ) public String updateSysUser(SysUser sysUser){ return "sucessUpdateSysUser" ; } @RequestMapping ( "/a/getall" ) public SysUser getAll(String userName){ return sysUserService.getAll(userName); } } |
11、编写userdetailservie的实现类,该类用于实现userdetail对象,用于验证账号和密码
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 | package cn.yinmingneng.yinmingneng.config; import cn.yinmingneng.yinmingneng.Services.SysUserService; import cn.yinmingneng.yinmingneng.entities.SysUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; 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; import java.util.ArrayList; import java.util.Collection; import java.util.List; @Service public class UserDetailimpl implements UserDetailsService { @Autowired private SysUserService sysUserService; /** * 从数据库获取用户数据 * @param s * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { Collection<GrantedAuthority> authorities = new ArrayList<>(); SysUser user = sysUserService.getAll(s); // 判断用户是否存在 if (user == null ) { throw new UsernameNotFoundException( "用户名不存在" ); } if (user.getUsername() != null && user.getUsername()!= "" ) authorities.add( new SimpleGrantedAuthority(user.getRolename())); // 返回UserDetails实现类 return new User(user.getUsername(), user.getPassword(), authorities); } } |
12、登录认证功能,spingSecutiry提供登录认证功能,请求/login会触发该方法,使用post请求
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 | package cn.yinmingneng.yinmingneng.filters; import cn.yinmingneng.yinmingneng.Services.TokenService; import cn.yinmingneng.yinmingneng.entities.SysUser; import com.fasterxml.jackson.databind.deser.impl.JavaUtilCollectionsDeserializers; 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.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * 此类用于登录认证,有/login触发,需要post请求 */ public class loginfilter extends UsernamePasswordAuthenticationFilter { @Autowired private AuthenticationManager authenticationManager; public loginfilter(AuthenticationManager a){ this .authenticationManager = a; } /** * 验证账号和密码 * @param request * @param response * @return * @throws AuthenticationException */ @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { String username= request.getParameter( "username" ); String password = request.getParameter( "password" ); // // return super.attemptAuthentication(request, response); //验证账号和密码 return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(username,password, Collections.emptyList())); } /** * 认证成功,生成token,返回token * @param request * @param response * @param chain * @param authResult * @throws IOException * @throws ServletException */ @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { // super.successfulAuthentication(request, response, chain, authResult); request.setCharacterEncoding( "UTF-8" ); response.setCharacterEncoding( "UTF-8" ); String username= request.getParameter( "username" ); Map<String,Object> map = new HashMap<>(); // map.put("id",sysUser.getId()); map.put(TokenService.userKey,username); // map.put(TokenService.userKey,) String token = TokenService.CreateToken(map); response.getWriter().write( "认证成功,token:" +token); } } |
13、生成token,我使用了前后端分离功能,所以需要生成token,写了TokenService类,用于解析生成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 | package cn.yinmingneng.yinmingneng.Services; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.stereotype.Component; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; public class TokenService { public static String userKey= "user_id" ; private static String signKey= "rererrertretretrettretert" ; public static String CreateToken(Map<String, Object> map) { Date now = new Date(System.currentTimeMillis()); String token = Jwts.builder() .setClaims(map) // 设置自定义数据 // .setIssuedAt(now) // 设置签发时间 .setExpiration( new Date(System.currentTimeMillis()+ 1000 * 60 * 60 * 360 )) // 设置过期时间 //.setIssuer(issuer) // 设置签发者 // .setSubject(subject) // 设置面向用户 .signWith(SignatureAlgorithm.HS512, signKey) .compact(); return token; } /** * 解压token,获取里面的令牌, * 自动判断有效期 */ public static Map parseToken(String t) { try { return Jwts.parser() .setSigningKey(signKey) .parseClaimsJws(t.replace( "Bearer " , "" )) .getBody(); } catch (Exception e) { throw new IllegalStateException( "Token验证失败:" + e.getMessage()); } } } |
13、携带token判断权限,token,用于判断token,判断成功就写入角色权限,该类无法自动注入SysUserService,需要手工注入,手工注入的代码在下面
BasicAuthenticationFilter 该类继承了oprefilter过滤器
package cn.yinmingneng.yinmingneng.filters;
import cn.yinmingneng.yinmingneng.Services.SysUserService;
import cn.yinmingneng.yinmingneng.Services.TokenService;
import cn.yinmingneng.yinmingneng.Utils.BeanFactoryUtil;
import cn.yinmingneng.yinmingneng.entities.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
private static final PathMatcher pathMatcher = new AntPathMatcher();
private SysUserService sysUserService;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
//认证token
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null) {
//验证token,并且写入权限
Map map = TokenService.parseToken(token);
String username = (String) map.get(TokenService.userKey);
if (username != null) {
//无法自动注入,只能手工注入,不知道为啥
sysUserService= BeanFactoryUtil.getBean(SysUserService.class);
SysUser sysUser = sysUserService.getAll(username);
// 这里直接注入角色,因为JWT已经验证了用户合法性,所以principal和credentials直接为null即可
List<GrantedAuthority> list = new ArrayList<>();
list.add(new SimpleGrantedAuthority(sysUser.getRolename()));
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(sysUser,
null, list);
// 如果验证失败,设置异常;否则将UsernamePasswordAuthenticationToken注入到框架中
if (authentication == null) {
//手动设置异常
request.getSession().setAttribute("SPRING_SECURITY_LAST_EXCEPTION",
new AuthenticationCredentialsNotFoundException("权限认证失败"));
// 转发到错误Url
request.getRequestDispatcher("/login/error").forward(request, response);
} else {
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}
filterChain.doFilter(request, response);
}
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /** * 手工获取注入对象 */ @Component public class BeanFactoryUtil implements ApplicationContextAware { private static ApplicationContext context = null ; public static <T> T getBean(Class<T> type) { return context.getBean(type); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (BeanFactoryUtil.context == null ) { BeanFactoryUtil.context = applicationContext; } } } |
14、springboot 接口时间映射处理,如果不处理会提示 bad request,"Bad Request",时间处理配置,此时就不用转换date了,直接映射成date
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /* * 增加时间处理格式,否则接口无法映射时间,会报错 * */ @Configuration public class WebMvcConfigurerIMP implements WebMvcConfigurer { /** * 自动转换时间格式 * * @param registry date */ @Override public void addFormatters(FormatterRegistry registry) { registry.addFormatter( new DateFormatter( "yyyy-MM-dd HH:mm:ss" )); } } |
15、用户密码是增加加密方式,注册的时候也需要加密方式,加密方式配置,配置文件配置,注册的时候密码也需要增加加密
1 2 | //使用加密方式 auth.userDetailsService(userDetailimpl).passwordEncoder(bCryptPasswordEncoder()); |
1 2 3 4 5 6 7 8 9 | @PostMapping ( "/insertsysuser" ) public int insertSysUser(SysUser sysUser) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); // Date date =sdf.parse("2021-1-1 09:35:00"); BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); sysUser.setPassword( passwordEncoder.encode(sysUser.getPassword())); // sysUser.setCreatetime(date); return sysUserService.insertSysUser(sysUser); } |
16、Springboot 登录认证失败处理类,该类主要用于验证失败返回错误信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Component public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable { private static final long serialVersionUID = -8970718410437077606L; @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException { int code = HttpStatus.UNAUTHORIZED; String msg = StringUtils.format( "请求访问:{},认证失败,无法访问系统资源" , request.getRequestURI()); ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg))); } } |
posted on 2021-11-02 22:59 topguntopgun 阅读(414) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
2020-11-02 ABP 生成Application.xml 文件无法复制到Host目录下面