springboot學習-Spring Security的配置(1)

在web應用開發中,安全無疑是十分重要的,選擇Spring Security來保護web應用是一個非常好的選擇。Spring Security 是spring項目之中的一個安全模塊,可以非常方便與spring項目無縫集成。特別是在spring boot項目中加入spring security更是十分簡單。本篇我們介紹spring security,以及spring security在web應用中的使用。

官網:https://spring.io/projects/spring-security

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements

在實際應用中,用戶的基本信息以及角色等都存儲在數據庫中,因此需要在數據庫中獲取數據進行認證。

文章將會介紹如何使用數據庫中獲取的數據進行認證和授權。

1.設計數據庫

  首先需要設計一個基本的用戶角色表。

user
id int(11) 主鍵;自增
username varchar(32) DEFAULT NULL
password varchar(255) DEFAULT NULL
enabled tinyint(1) DEFAULT NULL
locked tinyint(1) DEFAULT NULL
role
id int(11) 主鍵;自增
name varchar(32) DEFAULT NULL
nameZh varchar(32) DEFAULT NULL
user_role
id int(11) 主鍵;自增
uid int(11) DEFAULT NULL
rid int(11) DEFAULT NULL
/*
Navicat MySQL Data Transfer

Source Server         : localhost_3306
Source Server Version : 50625
Source Host           : localhost:3306
Source Database       : security

Target Server Type    : MYSQL
Target Server Version : 50625
File Encoding         : 65001

Date: 2020-02-02 11:10:08
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `role`
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) DEFAULT NULL,
  `nameZh` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('1', 'dba', '數據庫管理員');
INSERT INTO `role` VALUES ('2', 'admin', '系統管理員');
INSERT INTO `role` VALUES ('3', 'user', '用戶');

-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `enabled` tinyint(1) DEFAULT NULL,
  `locked` tinyint(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'root', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', '1', '0');
INSERT INTO `user` VALUES ('2', 'admin', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', '1', '0');
INSERT INTO `user` VALUES ('3', 'sang', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', '1', '0');

-- ----------------------------
-- Table structure for `user_role`
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` int(11) DEFAULT NULL,
  `rid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES ('1', '1', '1');
INSERT INTO `user_role` VALUES ('2', '1', '2');
INSERT INTO `user_role` VALUES ('3', '2', '2');
INSERT INTO `user_role` VALUES ('4', '3', '3');

2.創建項目

使用mybatis,創建spring boot web項目添加如下依賴:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

3.配置數據庫

在application.properties中進行數據庫連接配置:

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=abcdABCD123
spring.datasource.url=jdbc:mysql:///security

4.創建實體類

  分別創建角色表和用戶表對應的實體類,代碼如下:

public class Role {
    private Integer id;
    private String name;
    private String nameZh;
    //省略getter/setter

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNameZh() {
        return nameZh;
    }

    public void setNameZh(String nameZh) {
        this.nameZh = nameZh;
    }
}
public class User implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private Boolean enabled;
    private Boolean locked;
    private List<Role> roles;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (Role role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }
    @Override
    public String getPassword() {
        return password;
    }
    @Override
    public String getUsername() {
        return username;
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return !locked;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return enabled;
    }
    //省略getter/setter

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

//    public Boolean getEnabled() {
//        return enabled;
//    }

    public void setEnabled(Boolean enabled) {
        this.enabled = enabled;
    }

    public Boolean getLocked() {
        return locked;
    }

    public void setLocked(Boolean locked) {
        this.locked = locked;
    }

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }
}

代碼解釋:

4.1用戶實體類需要實現UserDetails接口,並實現該接口中的7個方法

方法名 解釋
getAuthorities(); 獲取當前用戶對象所具有的角色信息
getPassword(); 獲取當前用戶對象的密碼
getUsername(); 獲取當前對象的用戶名
isAccountNonExpired(); 當前賬戶是否未過期
isAccountNonLocked(); 當前賬戶是否未鎖定
isCredentialsNonExpired(); 當前賬戶密碼是否未過期
isEnabled(); 當前賬戶是否可用

4.2用戶根據實際情況設置這7個方法的返回值。因爲默認情況下不需要開發者自己進行密碼角色等信息的比對,開發者只需要提供相關信息即可,例如getPassword()方法返回的密碼和用戶輸入的登錄密碼不匹配,會自動跑出BadCredentialsException異常,isAccountNonExpired()方法返回了false,會自動拋出AccountExpiredException異常,因此對開發者而言,只需要對數據庫中的數據在這裏返回相應的配置即可。本案例因爲在數據庫中只有enabled和locked字段,故賬戶未過期和密碼未過期兩個方法都返回true。

4.3getAuthorities()方法用來獲取當前用戶所具有的角色信息,本案例中,用戶所具有的角色存儲在roles屬性中,因此該方法直接遍歷roles屬性,然後構造SimpleGrandAuthority集合並返回。

5.創建UserService

UserMapper和UserMapper.xml

@Mapper
public interface UserMapper {
    User loadUserByUsername(String username);
    List<Role> getUserRolesByUid(Integer id);
}
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.sang.security02.mapper.UserMapper">
    <select id="loadUserByUsername" resultType="org.sang.security02.model.User">
        select * from user where username=#{username}
    </select>
    <select id="getUserRolesByUid" resultType="org.sang.security02.model.Role">
        select * from role r,user_role ur where r.id=ur.rid and ur.uid=#{id}
    </select>
</mapper>

接下來創建UserService,代碼如下:

@Service
public class UserService implements UserDetailsService {
    @Autowired
    UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("賬戶不存在!");
        }
        user.setRoles(userMapper.getUserRolesByUid(user.getId()));
        return user;
    }
}

代碼解釋:

   *  定義UserService實現UserDetailsService接口,並實現該接口中的loadUserByUsername方法,該方法的參數就是用戶登錄時輸入的用戶名,通過用戶名去數據庫中查找用戶,如果沒有查找到用戶,就拋出一個賬戶不存在的異常,如果查到了用戶,就繼續查找用戶所具有的角色信息,並將獲取到的user對象返回,再由系統提供的DaoAuthenticationProvide類去比對密碼是否正確。

  * loadUserByUsername方法將在用戶登錄時自動調用。

6.配置Spring Security

接下來對Spring Security進行配置,代碼如下:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserService userService;
    @Bean
    RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        String hierarchy = "ROLE_dba > ROLE_admin ROLE_admin > ROLE_user";
        roleHierarchy.setHierarchy(hierarchy);
        return roleHierarchy;
    }
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setSecurityMetadataSource(cfisms());
                        object.setAccessDecisionManager(cadm());
                        return object;
                    }
                })
                .and()
                .formLogin()
                .loginProcessingUrl("/login").permitAll()
                .and()
                .csrf().disable();
    }
}

接下來創建Controller進行測試即可。。。

 

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