很久以前就想搭建一个属于自己的博客网站。即可以提高自己的编程能力,也可以让自己的博客生产量提高一些,终于在经历了myblog1.0、2.0的版本之后,今天“删库”,重新开始搭建我的个人blog
一 所使用的技术栈
我是一名后端开发人员(在校大二学生),所以我的技术栈以后端为主。前端我是找的UI框架,现在的UI框架有很多,改一改就可以用了。以下是我使用的技术栈
工具 | 名称 |
---|---|
编译器 | idea |
编程语言 | JAVA1.8 |
数据库 | mysql 8.0 |
项目框架 | SSM |
权限控制 | spring security |
缓存 | redis |
构建工具 | Maven |
接口调试 | swagger |
二 功能需求分析
1.用户管理
- 注册
- 登录
- 修改密码
- 增加用户
- 删除用户
- 搜索用户
2.安全管理
- 角色授权
- 权限设置
3.博客管理
- 发表博客
- 编辑博客
- 删除博客
- 博客分类
- 设置标签
- 上传图片
- 模糊查询
- 最新排序
- 最热排序
- 阅读量统计
4.评论管理
- 发表评论
- 删除评论
- 统计评论数
5.点赞管理
- 点赞
- 取消点赞
- 点赞量统计
6.分类管理
- 创建分类
- 编辑分类
- 删除分类
- 按分类查询
7.标签管理
- 创建标签、
- 删除标签
- 按标签查询
8.首页搜索
- 全文检索
- 最新文章
- 最热文章(阅读量 点赞量)
- 热门标签
- 热门用户
- 热门文章
三 用户登录
数据库User表
后台对应User类
这里我是用的是mybatis自动生成代码工具 mybatis-generator:generate -e 网上教程很多,用起来也很方便 不做多余阐述
public class User implements Serializable {
private Long id;
private String phoneNumber;
private String password;
private Integer role;
private String username;
private String trueName;
private String email;
private String birthday;
private String headPortrait;
private static final long serialVersionUID = 1L;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber == null ? null : phoneNumber.trim();
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password == null ? null : password.trim();
}
public Integer getRole() {
return role;
}
public void setRole(Integer role) {
this.role = role;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username == null ? null : username.trim();
}
public String getTrueName() {
return trueName;
}
public void setTrueName(String trueName) {
this.trueName = trueName == null ? null : trueName.trim();
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email == null ? null : email.trim();
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday == null ? null : birthday.trim();
}
public String getHeadPortrait() {
return headPortrait;
}
public void setHeadPortrait(String headPortrait) {
this.headPortrait = headPortrait == null ? null : headPortrait.trim();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", id=").append(id);
sb.append(", phoneNumber=").append(phoneNumber);
sb.append(", password=").append(password);
sb.append(", role=").append(role);
sb.append(", username=").append(username);
sb.append(", trueName=").append(trueName);
sb.append(", email=").append(email);
sb.append(", birthday=").append(birthday);
sb.append(", headPortrait=").append(headPortrait);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();
}
}
登录方式采用JWT的登录方式 即JSON WEB TOKEN
- 生成token工具类
public class JwtTokenUtil {
public static final long seriaVersionUID = -3301605591108950415L;
static final String CLAIM_KEY_USERNAME = "sub";
static final String CLAIM_KEY_AUDIENCE = "audience";
static final String CLAIM_KEY_CREATED = "created";
private static final String AUDIENCE_UNKNOWN = "unknown";
private static final String AUDIENCE_WEB = "web";
private static final String AUDIENCE_MOBILE = "mobile";
private static final String AUDIENCE_TABLET = "tablet";
//当前的签名的秘钥
private String secret = "blog";
//token的有效时间 约25min
private Long expiration = 1296000L;
public String getUsernameFromToken(String token) {
String username;
try {
final Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
public Date getCreatDateFromToken(String token) {
Date created;
try {
final Claims claims = getClaimsFromToken(token);
created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
} catch (Exception e) {
created = null;
}
return created;
}
//得到token的有效期
private Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
}
public String getAudienceFromToken(String token) {
String audience;
try {
final Claims claims = getClaimsFromToken(token);
audience = (String) claims.get(CLAIM_KEY_AUDIENCE);
} catch (Exception e) {
audience = null;
}
return audience;
}
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
//设置过期时间
private Date generateExpeirationDate() {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
private Boolean isCreatedAfterTenMinutes(Date created) {
int minutes = (int) ((System.currentTimeMillis() - created.getTime()) / (1000 * 60));
if (minutes >= 10) {
return true;
}
return false;
}
private String generateAudience(Device device) {
String audience = AUDIENCE_UNKNOWN;
if (device.isNormal()) {
audience = AUDIENCE_WEB;
} else if (device.isTablet()) {
audience = AUDIENCE_TABLET;
} else if (device.isMobile()) {
audience = AUDIENCE_MOBILE;
}
return audience;
}
private Boolean ignoreTokenExpiration(String token) {
String audience = getAudienceFromToken(token);
return (AUDIENCE_TABLET.equals(audience) || AUDIENCE_MOBILE.equals(audience));
}
String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpeirationDate())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
//判断是否在10分钟后并在有效期内
public Boolean canTokenBeRefreshed(String token) {
final Date created = getCreatDateFromToken(token);
return token != null && created != null && isCreatedAfterTenMinutes(created)
&& (!isTokenExpired(token)) || ignoreTokenExpiration(token);
}
public String refreshToken(String token) {
String refreshedToken;
try {
final Claims claims = getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
log.info("获取要刷新的token: {}", refreshedToken);
return refreshedToken;
}
public Boolean validateToken(String token, UserDetails userDetails) {
JwtUser user = (JwtUser) userDetails;
final String username = getUsernameFromToken(token);
final Date created = getCreatDateFromToken(token);
return (username.equals(user.getUsername())) && !isTokenExpired(token);
}
}
- 配置自己的拦截器
@Component
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
@Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setHeader("Access_Control_Allow_Origin","*");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("text/html; charset=utf-8");
ResultVO result = ResultVOUtil.error(ResultEnum.AUTHENTICATION_ERROR);
log.info("需要身份认证:{}" ,result);
httpServletResponse.getWriter().append(JSON.toJSONString(result));
}
}
@Component
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
@Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setHeader("Access_Control_Allow_Origin","*");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("text/html; charset=utf-8");
ResultVO result = ResultVOUtil.error(ResultEnum.AUTHENTICATION_ERROR);
log.info("需要身份认证:{}" ,result);
httpServletResponse.getWriter().append(JSON.toJSONString(result));
}
}
@Component
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
@Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setHeader("Access_Control_Allow_Origin","*");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("text/html; charset=utf-8");
ResultVO result = ResultVOUtil.error(ResultEnum.AUTHENTICATION_ERROR);
log.info("需要身份认证:{}" ,result);
httpServletResponse.getWriter().append(JSON.toJSONString(result));
}
}
- Security User
@Component
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
@Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setHeader("Access_Control_Allow_Origin","*");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("text/html; charset=utf-8");
ResultVO result = ResultVOUtil.error(ResultEnum.AUTHENTICATION_ERROR);
log.info("需要身份认证:{}" ,result);
httpServletResponse.getWriter().append(JSON.toJSONString(result));
}
}
@Service
@Slf4j
public class JwtUserDetailServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String phoneNum) throws UsernameNotFoundException {
User user = userService.getUserByPhoneNum(phoneNum);
if (user == null) {
log.info("此用户不存在");
throw new UsernameNotFoundException(String.format("用户名为 %s 的用户不存在", phoneNum));
} else {
String role = RoleEnum.getRole(user.getRole());
return new JwtUser(phoneNum, user.getPassword(), role);
}
}
}
- WebSecurity Config
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(this.userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationTokenFilter();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
//token的验证方式不需要开启csrf的防护
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
//设置无状态的连接,即不创建session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 当前的url允许进行匿名访问,即不需要身份认证
.antMatchers(
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
//配置swagger界面的匿名访问
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/images/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/v2/api-docs").permitAll()
.antMatchers("/configuration/ui").permitAll()
.antMatchers("/configuration/security").permitAll()
//配置允许匿名访问的路径
.antMatchers("/login").permitAll()
.anyRequest().authenticated();
//配置自己的验证过滤器
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity.headers().cacheControl();
}
}
- 自定义注解控制权限
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RoleContro {
RoleEnum role();
}
示例:
//此时只有权限为User即注册用户才有权限访问这个接口
@RoleContro(role = RoleEnum.USER)
@PostMapping(name = "修改个人信息", value = "/updateUser")
public void updateUser() {
//TODO 用户修改or完善个人信息
}
- UserService
/**
* 用户服务接口
* * @author Hobo
*/
public interface UserService {
/**
* 根据手机号码查询用户
*
* @param phoneNumber
* @return User
* @author hobo
*/
public User getUserByPhoneNum(String phoneNumber);
/**
* 通过token解析用户
*
* @return User
* @author hobo
*/
public User getCurrentUser();
/***
* 用户登录
* @param loginForm
* @param response
* @author hobo
* @return java.lang.Object
*/
public Object login(LoginForm loginForm, HttpServletResponse response);
}
- UserService实现类
/**
* 用户服务接口
* * @author Hobo
*/
public interface UserService {
/**
* 根据手机号码查询用户
*
* @param phoneNumber
* @return User
* @author hobo
*/
public User getUserByPhoneNum(String phoneNumber);
/**
* 通过token解析用户
*
* @return User
* @author hobo
*/
public User getCurrentUser();
/***
* 用户登录
* @param loginForm
* @param response
* @author hobo
* @return java.lang.Object
*/
public Object login(LoginForm loginForm, HttpServletResponse response);
}
- loginForm
@Data
public class LoginForm {
@NotNull(message = "手机号不能为空")
@ApiModelProperty("手机号码")
private String phoneNum;
@NotNull(message = "密码")
private String password;
}
- LoginController
@RequestMapping("/login")
public class LoginController {
@Autowired
private UserService userService;
@PostMapping(name = "用户登录", value = "/login")
public Object login(LoginForm loginForm, HttpServletResponse response) {
return userService.login(loginForm, response);
}
}
如果有志同道合的朋友或者大佬指点~ 欢迎骚扰 qq:1056024860