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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章