Springboot+mybatis+security搭建个人博客网站的第一天(用户登录)

很久以前就想搭建一个属于自己的博客网站。即可以提高自己的编程能力,也可以让自己的博客生产量提高一些,终于在经历了myblog1.0、2.0的版本之后,今天“删库”,重新开始搭建我的个人blog

一 所使用的技术栈

我是一名后端开发人员(在校大二学生),所以我的技术栈以后端为主。前端我是找的UI框架,现在的UI框架有很多,改一改就可以用了。以下是我使用的技术栈

工具名称
编译器idea
编程语言JAVA1.8
数据库mysql 8.0
项目框架SSM
权限控制spring security
缓存redis
构建工具Maven
接口调试swagger

二 功能需求分析

1.用户管理

  1. 注册
  2. 登录
  3. 修改密码
  4. 增加用户
  5. 删除用户
  6. 搜索用户

2.安全管理

  1. 角色授权
  2. 权限设置

3.博客管理

  1. 发表博客
  2. 编辑博客
  3. 删除博客
  4. 博客分类
  5. 设置标签
  6. 上传图片
  7. 模糊查询
  8. 最新排序
  9. 最热排序
  10. 阅读量统计

4.评论管理

  1. 发表评论
  2. 删除评论
  3. 统计评论数

5.点赞管理

  1. 点赞
  2. 取消点赞
  3. 点赞量统计

6.分类管理

  1. 创建分类
  2. 编辑分类
  3. 删除分类
  4. 按分类查询

7.标签管理

  1. 创建标签、
  2. 删除标签
  3. 按标签查询

8.首页搜索

  1. 全文检索
  2. 最新文章
  3. 最热文章(阅读量 点赞量)
  4. 热门标签
  5. 热门用户
  6. 热门文章

三 用户登录

数据库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);
    }
}

github传送门

如果有志同道合的朋友或者大佬指点~ 欢迎骚扰 qq:1056024860

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值