1. 程式人生 > >基於springboot+bootstrap+mysql+redis搭建一套完整的許可權架構【二】【整合springSecurity】

基於springboot+bootstrap+mysql+redis搭建一套完整的許可權架構【二】【整合springSecurity】

        若需要整合我們的springSecurity,一種是直接使用springSecurity自帶的許可權架構,另外一種是使用我們自己設計的資料架構,本文所闡述的就是使用自己設計的RBAC許可權架構,因此我們要事先設計好使用者許可權架構的PDM如下圖所示,並建立我們的資料庫:資料庫名:hyll_springboot,以及我們的三張表:user、user_role、user_associate_role:


接著開啟我們的工程新建如下工程的目錄:


接著在我們的sys包底下新建entity和dao這兩個包:


        同時開啟我們的pom.xml引入該工程所需要的所有依賴,接著我們的IDEA會彈出一個框,我們點選import就自動會去maven給我們下載依賴,若你有自己的私有maven則將其指向自己的私有maven,若這邊有缺少不懂的直接去我的第一章的github上的原始碼中自己去copy下來:

<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<mysql.version>5.1.41</mysql.version>
		<guava.version>18.0</guava.version>
		<org.mapstruct.version>1.1.0.Final</org.mapstruct.version>
	</properties>

	<dependencies>

		<!-- 整合Druid資料庫連線池和監控 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.3</version>
		</dependency>

		<!-- 引入mybatis的支援 -->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.1</version>
		</dependency>

		<!-- 引入mapstruct的支援 -->
		<dependency>
			<groupId>org.mapstruct</groupId>
			<artifactId>mapstruct-jdk8</artifactId>
			<version>${org.mapstruct.version}</version>
		</dependency>

		<dependency>
			<groupId>org.mapstruct</groupId>
			<artifactId>mapstruct-processor</artifactId>
			<version>${org.mapstruct.version}</version>
		</dependency>

		<!--  Java EE 6 規範 JSR 330 -->
		<dependency>
			<groupId>javax.inject</groupId>
			<artifactId>javax.inject</artifactId>
			<version>1</version>
		</dependency>

		<!-- 引入json的依賴 classifier必須要加這個是json的jdk的依賴-->
		<dependency>
			<groupId>net.sf.json-lib</groupId>
			<artifactId>json-lib</artifactId>
			<version>2.4</version>
			<classifier>jdk15</classifier>
		</dependency>


		<!-- 開啟spring-websocket的支援 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>

		<!-- 開啟spring-security的支援 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<!-- 開啟thymeleaf的spring-security的支援 -->
		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-springsecurity4</artifactId>
		</dependency>

		<!-- 表示對thymeleaf模板不再是用預設的HTML5標準來做嚴格限制 -->
		<dependency>
			<groupId>net.sourceforge.nekohtml</groupId>
			<artifactId>nekohtml</artifactId>
			<version>1.9.22</version>
		</dependency>

		<!-- 新增對spring-redis的支援 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-redis</artifactId>
			<version>1.3.8.RELEASE</version>
		</dependency>

		<!-- 新增對spring-cache的支援 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>

		<dependency>
			<groupId>net.sf.ehcache</groupId>
			<artifactId>ehcache</artifactId>
		</dependency>

		<!-- 新增對spring-data-rest的支援 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-rest</artifactId>
		</dependency>

		<!-- 新增對spring-jpa的支援 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>${mysql.version}</version>
		</dependency>

		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>${guava.version}</version>
		</dependency>

		<!-- 新增對thymeleaf的支援 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

		<!-- 新增對websocket的支援 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>

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

		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<version>1.3.5.RELEASE</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional><!-- optional=true,依賴不會傳遞,該專案依賴devtools;之後依賴myboot專案的專案如果想要使用devtools,需要重新引入 -->
		</dependency>

		<dependency>
			<groupId>com.xiaoleilu</groupId>
			<artifactId>hutool-all</artifactId>
			<version>3.0.9</version>
		</dependency>

		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.6.1</version>
		</dependency>

		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.6.1</version>
		</dependency>
		<dependency>
			<groupId>com.vaadin.external.google</groupId>
			<artifactId>android-json</artifactId>
			<version>0.0.20131108.vaadin1</version>
		</dependency>

	</dependencies>
同時在我們的entity包底下新建我們剛剛的三個實體:
/**
 *@author linzf
 **/
public class User implements UserDetails {

	public User(){
		super();
	}

	public User(int id){
		this.id = id;
	}

	private int id;
	private String login;
	private String password;
	private String userName;
	private String address;
	private String job;
	private long groupId;
	private Date birthDate;
	private String city;
	private String district;
	private String province;
	private String streetAddress;
	private String state;
	private String type;
	private Date lastLoginDate;
	// 使用者角色資訊
	private List<UserRole> roles;
	// 許可權集合資料
	private String roleArray;

	public String getRoleArray() {
		return roleArray;
	}

	public void setRoleArray(String roleArray) {
		this.roleArray = roleArray;
	}

	public int getId() {
		return id;
	}

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

	public String getLogin() {
		return login;
	}

	public void setLogin(String login) {
		this.login = login;
	}

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
		if(this.getRoles()!=null){
			List<UserRole> roles=this.getRoles();
			for(UserRole role:roles){
				if(role.getName()!=null){
					auths.add(new SimpleGrantedAuthority(role.getName()));
				}
			}
		}
		return auths;
	}

	public String getPassword() {
		return password;
	}

	@Override
	public String getUsername() {
		return this.getUserName();
	}

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

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

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

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

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

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

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

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public String getJob() {
		return job;
	}

	public void setJob(String job) {
		this.job = job;
	}

	public long getGroupId() {
		return groupId;
	}

	public void setGroupId(long groupId) {
		this.groupId = groupId;
	}

	public Date getBirthDate() {
		return birthDate;
	}

	public void setBirthDate(Date birthDate) {
		this.birthDate = birthDate;
	}

	public String getCity() {
		return city;
	}

	public void setCity(String city) {
		this.city = city;
	}

	public String getDistrict() {
		return district;
	}

	public void setDistrict(String district) {
		this.district = district;
	}

	public String getProvince() {
		return province;
	}

	public void setProvince(String province) {
		this.province = province;
	}

	public String getStreetAddress() {
		return streetAddress;
	}

	public void setStreetAddress(String streetAddress) {
		this.streetAddress = streetAddress;
	}

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public Date getLastLoginDate() {
		return lastLoginDate;
	}

	public void setLastLoginDate(Date lastLoginDate) {
		this.lastLoginDate = lastLoginDate;
	}

	/**
	 * 功能描述:組裝角色資料集合
	 * @param roleArray
	 */
	public void packagingRoles(String roleArray){
		List<UserRole> roles = new ArrayList<UserRole>();
		if(roleArray!=null){
			UserRole userRole = null;
			for(String roleId:roleArray.split(",")){
				if(!roleId.isEmpty()){
					userRole = new UserRole();
					userRole.setId(Long.parseLong(roleId));
					roles.add(userRole);
				}
			}
		}
		this.setRoles(roles);
	}

}
/**
 *@author linzf
 **/
public class UserRole {
	private long id;
	private String name;
	private String roleName;

	public long getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

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

	public String getRoleName() {
		return roleName;
	}

	public void setRoleName(String roleName) {
		this.roleName = roleName;
	}


}

/**
 *@author linzf
 **/
public class UserAssociateRole {
	private int userId;
	private long roleId;

	public UserAssociateRole(){
		super();
	}

	public UserAssociateRole(int userId,long roleId){
		this.userId = userId;
		this.roleId = roleId;
	}

	public int getUserId() {
		return userId;
	}

	public void setUserId(int userId) {
		this.userId = userId;
	}

	public long getRoleId() {
		return roleId;
	}

	public void setRoleId(long roleId) {
		this.roleId = roleId;
	}

}

接著我們在dao包裡面建立以下的介面:
/**
 *@author linzf
 **/
public interface UserDao {

    /**
     * 功能描述:根據賬號來獲取使用者資訊
     * @param login
     * @return
     */
    User findByLogin(String login);

	
}

接著我們引入我們的mybatis配置以及我們的security和快速切換環境配置,首先在我們的application.properties底下增加以下配置:
spring.profiles.active=dev

#配置放行的目錄和方法
security.ignored=/api/*,/css/*,/js/*,/images/*,/fonts/*,/font-awesome/*
#表示對thymeleaf模板不再是用預設的HTML5標準來做嚴格限制
spring.thymeleaf.mode = LEGACYHTML5

#配置mybatis的掃描的包的檔案的入口
mybatis.config-locations=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

同時在我們的resource目錄底下建立一個目錄mybatis並在該目錄底下建立一個檔案mybatis-config.xml和mapper目錄如下所示:


mybatis-config.xml程式碼如下所示:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <typeAlias alias="Integer" type="java.lang.Integer" />
        <typeAlias alias="Long" type="java.lang.Long" />
        <typeAlias alias="HashMap" type="java.util.HashMap" />
        <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
        <typeAlias alias="ArrayList" type="java.util.ArrayList" />
        <typeAlias alias="LinkedList" type="java.util.LinkedList" />
    </typeAliases>
</configuration>

同時在我們的resource目錄底下建立我們的application-dev.properties檔案資訊如下:
server.port = 8080
#資料庫連線配置
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://10.6.71.236:3306/hyll_springboot?characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=haoyunll123
接著我們在resource/mapper目錄底下建立一個mybatis_user.xml內容如下:
<?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.csdn.demo.sys.dao.UserDao">

	<!-- 包含角色資訊的map -->
	<resultMap type="com.csdn.demo.sys.entity.User" id="UserLoginMap">
		<id property="id" column="id"/>
		<result property="login" column="login"/>
		<result property="password" column="password"/>
		<result property="userName" column="user_name"/>
		<result property="address" column="address"/>
		<result property="job" column="job"/>
		<result property="groupId" column="group_id"/>
		<result property="birthDate" column="birth_date"/>
		<result property="city" column="city"/>
		<result property="district" column="district"/>
		<result property="province" column="province"/>
		<result property="streetAddress" column="street_address"/>
		<result property="state" column="state"/>
		<result property="type" column="type"/>
		<result property="lastLoginDate" column="last_login_date"/>
		<collection property="roles"  ofType="com.csdn.demo.sys.entity.UserRole" javaType="java.util.ArrayList">
			<result column="user_role_id" property="id" jdbcType="VARCHAR" />
			<result column="name" property="name" jdbcType="VARCHAR" />
			<result column="role_name" property="roleName" jdbcType="VARCHAR" />
		</collection>
	</resultMap>
	
	<!-- 根據賬號來獲取使用者資訊 -->
	<select id="findByLogin" parameterType="java.lang.String" resultMap="UserLoginMap">
		select u.*,ur.id as user_role_id,ur.name,ur.role_name from user u inner join user_associate_role uar on u.id = uar.user_id inner join user_role ur on uar.role_id = ur.id where u.login = #{login}
	</select>

</mapper>

接著開始我們的springsecurity的配置,找到我們的config包在該包底下我們建立一個security和mybatis包如下所示:



接著在我們的security增加以下三個類分別是(CustomPasswordEncoder:密碼加密類;CustomUserService:登陸邏輯重寫類;WebSecurityConfig:security實現配置類):

/**
 * spring-security登陸的密碼進行MD5加密傳到資料庫
 */
public class CustomPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence rawPassword) {
        Md5PasswordEncoder encoder = new Md5PasswordEncoder();
        return encoder.encodePassword(rawPassword.toString(), "hyll");
    }
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        Md5PasswordEncoder encoder = new Md5PasswordEncoder();
        return encoder.isPasswordValid(encodedPassword, rawPassword.toString(), "hyll");
    }

}

/**
 * Created by Administrator on 2017/8/4 0004.
 */
public class CustomUserService implements UserDetailsService {

    @Inject
    private UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        User user = userDao.findByLogin(s);
        if(user == null){
            throw new UsernameNotFoundException("使用者名稱不存在");
        }
        // 自定義錯誤的文章說明的地址:http://blog.csdn.net/z69183787/article/details/21190639?locationNum=1&fps=1
        if(user.getState().equalsIgnoreCase("0")){
            throw new LockedException("使用者賬號被凍結,無法登陸請聯絡管理員!");
        }
        return user;
    }
}

/**
 * 實現Security的配置
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    UserDetailsService customUserService(){
        return new CustomUserService();
    }

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

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

    }

    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    /**
     * 描述:csrf().disable()為了關閉跨域訪問的限制,若不關閉則websocket無法與後臺進行連線
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers().frameOptions().disable();
        http.csrf().disable().authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/main")
                .failureUrl("/login?error=true")
                .permitAll()
                .and()
                .logout()
                .logoutSuccessUrl("/login").
                permitAll();
    }

}

接著我們在mybatis包底下新增MyBatisConfig配置類如下所示【MapperScan掃描的是我們的dao介面的存放路徑,因此此處大家一定要注意自己的dao包的路徑是否正確,否則會導致呼叫dao方法出錯】:
@Configuration
@MapperScan("com.csdn.demo.*.dao")
public class MyBatisConfig {

}
接著在我們的config目錄底下建立我們的WebMvcConfig配置檔案如下所示:
/**
 * 類描述:springMVC的配置
 */
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    /**
     * 重寫方法描述:實現在url中輸入相應的地址的時候直接跳轉到某個地址
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/main").setViewName("main");
        registry.addViewController("/error").setViewName("error");
    }

}

  每次我們在maven重新import的時候我們需要重新將以下的一個配置重新設定下,否則我們的工程將無法執行起來如圖所示【file->project Structure】:


   到此處我們的整個基礎工程已經構建完成,我們可以直接將該工程執行起來,訪問http://127.0.0.1:8080/login,由於還沒有引入bootstrap因此整個頁面顯得不叫的醜,後續將bootstrap引入那麼你們就會發現我們的頁面越來越漂亮,執行效果如下圖所示:


到此處我們的工程已經上次成功了,我會將本章的程式碼直接上傳到github,大家可以直接下載下來並執行該程式碼,請大家在執行的時候先把整篇文章過一遍再執行,下一章將講解如何整合swagger2以及druid這兩個配置,本章程式碼的github地址是:https://github.com/185594-5-27/csdndemo/tree/base-demo,大家在匯入專案的時候記得要將版本切換到base-demo版本這個版本才是本章的程式碼。

QQ交流群:578746866