SpringBoot第六篇-SpringBoot+Mybatis+SpringSecurity+JWT整合,开发工具IDea
一、新建SpringBoot项目
,选择Maven,插件选择SpringWeb
二、引入springSecurity包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
三、设置启动配置类
//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的区别
@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包下面
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动态代理生成,我使用配置文件生成增删改查语句
@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和接口的方法名一样,否则会报错,映射不到
<?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代码,接口,方便接口化编程
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
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、控制器测试层,永远测试带权限功能,不带权限功能,是否登录功能,匿名访问功能
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对象,用于验证账号和密码
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请求
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
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);
}
}
}
/**
* 手工获取注入对象
*/
@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
/*
* 增加时间处理格式,否则接口无法映射时间,会报错
* */
@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、用户密码是增加加密方式,注册的时候也需要加密方式,加密方式配置,配置文件配置,注册的时候密码也需要增加加密
//使用加密方式
auth.userDetailsService(userDetailimpl).passwordEncoder(bCryptPasswordEncoder());
@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 登录认证失败处理类,该类主要用于验证失败返回错误信息
@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 阅读(439) 评论(0) 收藏 举报
浙公网安备 33010602011771号