三、用Spring Security实现登录

Hi,大家好,我是姜友华。
今天,我们一起来看看用户登录部分。这一部分涉及到Spring Security,让我们一步步来实现它。

这里有Spring Security 中文版手册可以看看。

一、添加spring-security

1. 在pom.xml添加spring-security支持。

......
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
......

2. 添加后更新Maven,运行,在浏览器上查看一下。

Spring security 登录

这里需要说明一下:

  • 添加Spring Security后,默认会拦截服务器的所有请求,并跳转到登录页面。
  • Spring Security不仅有默认的登录页面,还有默认的登录处理入口,即/login

3. 简单测试一下,是否可用。

  • resource/static/添加index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Root Index</title>
</head>
<body>
    <h1>I'm Root Index</h1>
</body>
</html>
  • com.muutr.shop添加config包后,再在config里添加SecurityConfig类。
package com.muutr.shop.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().passwordEncoder(NoOpPasswordEncoder.getInstance())
                .withUser("user").password("0000").roles("USER");
    }
}

运行看看。输入错误的用户名或密码,会显示错误信息;输入正确的用户名与密码,会跳转到首页。
需要注意的是:NoOpPasswordEncoder是一个过期的方法。这里我们只是用来测试Spring Security是否可用,在工程里,你千万别用。

二、使用数据库来实现登录。

1. 在resource/static/里添加目录结构如下:

> resource
    > static
        > admin
            - index.html
        > user
            - index.html
        - index.html
        - login.html
  • admin/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Admin Index</title>
</head>
<body>
    <h1>I'm Admin Index</h1>
</body>
</html>
  • user/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>User Index</title>
</head>
<body>
    <h1>I'm User Index</h1>
</body>
</html>
  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Root Index</title>
</head>
<body>
    <h1>I'm Root Index</h1>
</body>
</html>
  • login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>User Login</title>
</head>
<body>
<form action="/login" method="post">
    <input type="text" name="username" placeholder="Username">
    <input type="password" name="password" placeholder="PassWord">
    <input type="submit" value="Submit">
</form>
</body>
</html>

2. 在数据库shop里添加表user_register

  • 创建表。
CREATE TABLE user_register
(
    `id`          BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    `name`        VARCHAR(255) COMMENT '用户账号',
    `password`    VARCHAR(255) COMMENT '密码',
    `create_time` DATETIME            NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `update_time` DATETIME            NULL     DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
    `status` TINYINT(1)                        DEFAULT 1 COMMENT '用户是否有效',
    UNIQUE INDEX unique_name (`name`),
    PRIMARY KEY (`id`)
) COMMENT '用户注册表';
  • 插入数据。
INSERT INTO user_register (username, password)
VALUES ('jyh', '0000');
UPDATE user_register
SET password = '$2a$10$sFydHDKoaxkB5tEPhOWRtel0PPhaQKvHHfnBN36XsgTbXSKqucbDi'
WHERE id = 1;

这个password的一长串字符是啥?那是“0000”BCrypt编码。我们可以这样得到:

使用debug运行,在SecurityConfig方法里打断点,在Variables里添加(new BCryptPasswordEncoder()).encode("0000"),会输出这种字符串。

生成“0000”BCrypt编码

3. 添加Entity。

UserRegisteruser_register表对应,同时需要实现UserDetails

package com.muutr.shop.entity;

import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Date;

@Data
public class UserRegister implements UserDetails {
    private int id;
    private String username;
    private String password;
    private Date createTime;
    private Date updateTime;
    private Collection<? extends GrantedAuthority> authorities;

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

4. 添加Mapper。

  • userRegisterMapper类文件。
package com.muutr.shop.mapper;

import com.muutr.shop.entity.UserRegister;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface UserRegisterMapper {
    UserRegister selectUserByName(@Param("username") String name);
}
  • userRegisterMapperXML文件。
<?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="com.muutr.shop.mapper.UserRegisterMapper">
    <resultMap id="BaseResultMap" type="com.muutr.shop.entity.UserRegister" >
        <id column="id" property="id" jdbcType="BIGINT" />
        <result column="username" property="username" jdbcType="VARCHAR" />
        <result column="password" property="password" jdbcType="VARCHAR" />
        <result column="create_time" property="createTime" jdbcType="DATE" />
        <result column="update_time" property="updateTime" jdbcType="DATE" />
    </resultMap>
    <select id="selectUserByName" parameterType="java.lang.String" resultMap="BaseResultMap">
        select id, username, password, create_time, update_time from user_register where status = 1 AND username = #{username}
    </select>
</mapper>

5. 添加Service。

package com.muutr.shop.service.impl;

import com.muutr.shop.entity.UserRegister;
import com.muutr.shop.mapper.UserRegisterMapper;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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;

@Service
从数据库里查找用户,并设置他的权限。
public class UserDetailsServiceImpl implements UserDetailsService {
    private final UserRegisterMapper userRegisterMapper;

    public UserDetailsServiceImpl(UserRegisterMapper userRegisterMapper) {
        this.userRegisterMapper = userRegisterMapper;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserRegister user = userRegisterMapper.selectUserByName(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        user.setAuthorities(authorities);
        return user;
    }
}

注意,这里是ROLE_USER,所以权限名称都需要添加ROLE_前缀。

5. 改SecurityConfig的认证方式。

由内存认证改为自定义认证。从userDetailsService获取数据,使用BCrypt编码去认证。

package com.muutr.shop.config;

import com.muutr.shop.service.impl.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    private final UserDetailsServiceImpl userDetailsService;

    public SecurityConfig(UserDetailsServiceImpl userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

这里所有了BCryptPasswordEncoder()来进行BCrypt编码。
运行一下,可以成功登录。

登录成功跳转首页

三、用户权限划分。

1. 添加权限规则。

  • SecurityConfig文件里,添加configure方法。
......
    protected void configure(final HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasAnyRole("ADMIN", "USER")
                .antMatchers("/", "/**").permitAll()
                .and()
                .formLogin().defaultSuccessUrl("/user/index.html", true)
                .and()
                .logout();
    }
......

上面代码的意思是:

  • 我们来处理权限请求,优先级最高的排前面;
  • 访问admin文件夹里的,需要ADMIN权限;
  • 访问user文件夹里的,需USER权限;
  • 其余的,不需要权限;
  • 还有,使用表单登录,成功后转到/user/index.html
  • 还有,使用登出。
    进入了/user/index.html

2. 使用自定义的表单进行提交。

修改SecurifyConfig

  • 指定登录页面即处理权限的入口;
  • 关闭“csrf防护”。
    CSRF(Cross Site Request Forgery),中文是跨站点请求伪造。由于我们没有使用模板引擎,请求时没有带CSRF Token。
protected void configure(final HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasAnyRole("ADMIN", "USER")
                .antMatchers("/", "/**").permitAll()
                .and()
                .formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .defaultSuccessUrl("/user/index.html", true)
                .and()
                .logout()
                .and()
                .csrf().disable();
    }

运行是成功的。

3. 使用登出。

  • /user/index.html添加登出链接。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>User Index</title>
</head>
<body>
<a href="/logout">Logout</a>
<h1>I'm User Index</h1>
</body>
</html>

点击页面可的Logout,实现了登出。

好吧,就这些了。我是姜友华,下次见。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,061评论 6 523
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,407评论 3 404
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,275评论 0 368
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,084评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,091评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,555评论 1 315
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,914评论 3 429
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,900评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,438评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,470评论 3 346
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,596评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,187评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,932评论 3 340
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,361评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,511评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,163评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,671评论 2 366

推荐阅读更多精彩内容