1、新建SpringBoot項目,選擇Maven,插件選擇SpringWeb、SpringSecurity、Mysql Driver、Mybatis Framwork
2、創建controller,看看能否跑通,先設置配置文件application.yml的端口為8094,不喜歡用8080,因為太多程序調試使用它
server:
port: 8094
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost/8888?characterEncoding=utf-8&useSSl=false
driver-class-name: com.mysql.jdbc.Driver
mybatis:
mapper-locations: classpath*:mappers/*Mapper.xml
type-aliases-package: cn.yinmingneng.yinmingneng.entities
configuration:
map-underscore-to-camel-case: true
@RestController public class TestController { @RequestMapping("/gethello") public String getHello(){ return "你好!"; } }
3、輸入地址:http://localhost:8094/gethello,出現一下界面表示配置成功了
用戶名: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))); } }