这几天由于需求,整合了一下Springsecurity的安全框架,独乐乐不如众乐乐和大家分享一下,避免不必要的坑。## 标题
这里奉上GitHub克隆地址点击https://github.com/xjjxltzf/CSDN.git
进入正题,本次将采用本人的理解讲解该安全框架的整合。
项目结构:
一个小dome
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.demo.SpringBootMyBatisSpringSecurity</groupId> <artifactId>SpringBootMyBatisSpringSecurity</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>SpringBootMyBatisSpringSecurity</name> <description>SpringBootMyBatisSpringSecurity</description> <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!-- 模板引擎jar包, 已经包括了spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- Spring Boot Test 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- 用于thymeleaf中使用security的标签 --> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity4</artifactId> </dependency> <!-- Spring Boot Mybatis 依赖 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.1</version> </dependency> <!--mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.32</version> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- Junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <verbose>true</verbose> <fork>true</fork> <executable>${JAVA8_HOME}/bin/javac</executable> </configuration> </plugin> </plugins> </build> </project>
整合采用yml+mybatis-config.xml进行配置
spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql:///数据库名?serverTimezone=UTC username: 你的数据库用户名 password: 你的数据库密码 mybatis: #这里是Mybatis的配置文件映射路径 config-location: classpath: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> <settings> <setting name="useGeneratedKeys" value="true"/> <!-- 完全映射 --> <setting name="autoMappingBehavior" value="FULL"/> <!-- 开启懒加载 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 按需加载 --> <setting name="aggressiveLazyLoading" value="false"/> <!-- 开启全局缓存 --> <setting name="cacheEnabled" value="true"/> </settings> <!-- 类型别名 --> <typeAliases> <package name="com.tao.pojo"/> </typeAliases> </configuration>
*---------------------------------------------------------配置搞定开始撸码-------------------------------------------------
国际惯例
com.demo.pojo
>DbUser.java
package com.demo.pojo; import lombok.Data; /* * 测试用户对象 */ @Data public class DbUser { private String username;//用户名 private String password;//用户密码 private Integer access_level;//用户权限标识 private String description;//用户权限描述 }
com.demo.dao
>DataMapper.java
package com.demo.dao; import com.demo.pojo.DbUser; /* * Mybatis数据映射, */ public interface DataMapper { DbUser getUserLoginInfo(String username); }
>DataMapper.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.demo.DataMapper" > <select id="getUserLoginInfo" resultType ="com.demo.pojo.DbUser" > select username, password, access_level,description from demo_security_user where username= #{username} </select> </mapper>
com.demo.impl
这里开始写有关Springsecurity的代码,此实现层功能描述:调用DAO层通过用户输入的用户名查询用户记录返回用户信息
package com.demo.impl; import java.util.ArrayList; import java.util.Collection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; 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 com.demo.dao.DataMapper; import com.demo.pojo.DbUser; /* * 自定义用户名密码校验实现,一定要@Service注解,然后在配置类中加载(重载configure) */ @Service public class UserDetailInfo implements UserDetailsService{ //数据库操作 @Autowired private DataMapper dbDataMapper; //必须重写,自己来实现登陆验证 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { System.out.println("这里点击登录后进"); System.out.println("user["+username+"] is logining..."); DbUser dbUser = dbDataMapper.getUserLoginInfo(username); if(dbUser==null) { System.out.println("user["+username+"] is not exist!"); throw new UsernameNotFoundException(username + " do not exist!"); } System.out.println("Get user info from db: "+ dbUser.toString()); UserDetails user = new User(dbUser.getUsername(), dbUser.getPassword(), true, true, true, true, getAuthorities(dbUser.getAccess_level())); return user; } /** * 获得访问角色权限 */ public Collection<GrantedAuthority> getAuthorities(Integer access) { Collection<GrantedAuthority> authorities = new ArrayList<>(); //所有的用户默认拥有ROLE_USER权限 authorities.add(new SimpleGrantedAuthority("ROLE_USER")); if (access.compareTo(0) == 0) { // 如果参数access为0.则拥有ROLE_ADMIN权限 authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); }else if(access.compareTo(2) == 0) { // 如果参数access为2.则拥有ROLE_TAO权限 authorities.add(new SimpleGrantedAuthority("ROLE_TAO")); } //最终这里返回的是用户权限集合 return authorities; } }
**
**
com.demo.config
>WebSecurityConfig.java
package com.demo.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import com.demo.impl.UserDetailInfo; /* * 配置类: * 重写它的方法来设置一些web安全的细节,如配置security的登录页面和传递的参数,公共路径权限属性等 */ @Configuration @EnableGlobalMethodSecurity(securedEnabled = true) //控制权限到请求方法级别 //@EnableGlobalMethodSecurity(prePostEnabled = true)//方法调用前鉴权 @EnableWebSecurity //禁用Boot的默认Security配置,配合@Configuration启用自定义配置(需要扩展WebSecurityConfigurerAdapter) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //自定义认证对象 @Autowired private UserDetailInfo urlUserService; //HTTP请求安全处理 @Override protected void configure(HttpSecurity http) throws Exception { System.out.println("这里程序运行第二个进入"); //请求权限配置 http.authorizeRequests() //指定了/和/home不需要任何认证就可以访问, .antMatchers("/", "/login","/**").permitAll() //任何请求,登录后方可访问。 .anyRequest().authenticated() //登陆界面参数 .and().formLogin().loginPage("/login").defaultSuccessUrl("/hello")/*.usernameParameter("username").passwordParameter("password")*/.permitAll() //设置注销成功后跳转页面,默认是跳转到登录页面 .and().logout().logoutSuccessUrl("/login").permitAll() //权限访问失败界面,关键,如果不定义的话会抛出异常 .and().exceptionHandling().accessDeniedPage("/denied") ; http.csrf().disable(); } /* * 身份验证管理生成器。一定要重载!!!不然自定义的登陆校验不生效 * */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { System.out.println("不用测试了这里程序运行第一个进入"); auth.userDetailsService(urlUserService) /*.passwordEncoder(new PasswordEncoder() { //可以自己定义密码匹配规则 @Override public String encode(CharSequence rawPassword) { return (String)rawPassword;//MD5Util.encode((String) rawPassword); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { System.out.println(encodedPassword + "---" + (String)rawPassword); return encodedPassword.equals((String) rawPassword); } })*/; } }
MvcConfig.java
视图解析器可以少写controller中的映射代码
package com.demo.config; import org.springframework.stereotype.Component; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Component /** * 视图解析器 * @author TAO * */ public class MvcConfig extends WebMvcConfigurerAdapter { //直接页面跳转,不经过Controller,这样在没有任何处理业务的时候,快捷的页面转向定义会节省好多代码 @Override public void addViewControllers(ViewControllerRegistry registry) { //这里主要配置不需要任何认证就可以访问映射 registry.addViewController("/login").setViewName("login"); registry.addViewController("/").setViewName("login"); //registry.addViewController("/hello").setViewName("hello"); //registry.addViewController("/login").setViewName("login"); //registry.addViewController("/denied").setViewName("denied"); //registry.addViewController("/admin").setViewName("admin"); } }
com.demo.controller
>HelloController.java
package com.demo.controller; import org.springframework.security.access.annotation.Secured; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /* * 网络控制层:返回数据的controller。这里映射到resources目录下的templates的html页面。 * */ @Controller public class HelloController { //这里对比MvcConfig.java视图映射器,那里写了这里就可以少写这些代码 /*@RequestMapping("/") public String index() { return "home"; } @RequestMapping("/home") public String home() { return "home"; }*/ /*当我们访问这个URL的时候,Spring Security会帮我们验证当前用户是否有权限访问该地址。 *官方推荐的鉴权注解方式,控制权限到请求方法级别。可通过三种方式的注解: *注解方式1:@Secured, spring自带的注解方法:securedEnabled = true *注解方式2:@PreAuthorize,方法调用前注解:securedEnabled = true *注解方式2:@RolesAllowed,非spring框架: jsr250Enabled = true *注意1:角色要填全名! *注意2:一定要在自定义的WebSecurityConfigurerAdapter中添加注解。@EnableGlobalMethodSecurity(axx=bxx)!axx/bxx见上 */ @Secured("ROLE_USER") @RequestMapping("/hello") //@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_USER')") public String hello(){ return "hello"; } @RequestMapping(value = "/login") public String login() { return "login"; } @RequestMapping(value = "/logout") public String logout() { return "home"; } @RequestMapping(value = "/denied") public String denied() { return "denied"; } @Secured("ROLE_ADMIN") @RequestMapping(value = "/admin") //@PreAuthorize("hasAnyRole('ROLE_ADMIN')") public String admin() { return "admin"; } }
com.demo
>DemoApplication.java
程序入口
package com.demo; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication //这个表示mybatis自动扫描dao接口的包名, @MapperScan("com.demo") public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
*------------------------------------------java代码搞定开始静态界面-------------------------------------------------
login.html登入界面,hello.html欢迎页-用户权限页,denied.html-拒绝访问页,admin.html管理员页
*login.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Spring Security Example</title> </head> <body> <div th:if="${param.error}">无效的用户名和密码。.</div> <div th:if="${param.logout}">您已经注销了.</div> <h2>使用账号密码登录</h2> <form th:action="@{/login}" method="post"> <div> <label> User Name : <input type="text" name="username" /> </label> </div> <div> <label> Password : <input type="password" name="password" /> </label> </div> <div> <input type="submit" value="Sign In" /> </div> </form> </body> </html>
hello.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Hello World!</title> </head> <body style="background:url(backgrounds.gif) no-repeat 4px 5px ; height:100%; width:100%;"> <p>普通。每个人都可以参观。</p> <div sec:authorize="hasRole('ROLE_ADMIN')"> <p>这是管理员访问.</p> </div> <!-- 通过标签鉴权,对应不同角色显示不同信息 --> <div sec:authorize="hasRole('ROLE_USER')"> <p>这是用户访问。</p> </div> <div sec:authorize="hasRole('ROLE_TAO')"> <p>这是TAO才能访问</p> </div> <p> Click <a th:href="@{/logout}">here</a> 退出 </p> <p> Click <a th:href="@{/admin}">here</a> 去管理员页面 </p> </body> </html>
admin.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Admin Page</title> </head> <body> <h1>管理员页</h1> <p> Click <a th:href="@{/hello}">here</a> 普通界面 </p> <p> Click <a th:href="@{/logout}">here</a> 退出 </p> </body> </html>
denied.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>拒绝访问</title> </head> <body> <h1>您的访问被拒绝</h1> <p>这是一个拒绝访问的页面</p> <p> Click <a th:href="@{/hello}">here</a> 去你好页面 </p> <p> Click <a th:href="@{/logout}">here</a> 退出 </p> </body> </html>
login.html
有点简陋主要学习框架
这里直接/或者login都可以,其他映射路径全部拦截到login这里
登入成功后
用的是admin用户
图中的(这是用户访问。)
这里每个用户都有一个ROLE_USER这样的权限,解释一下:由于静态资源路径这样写/static/** 这样放不过,去所以自能写成/星星(这里/星星转义打不出理解一下你懂得)
然后所有请求都要配置一个@Secured(“ROLE_ADMIN”)这样的权限请求,(注意除/,和/login不然登入页面都看不到)
其实这里有三个权限提示但是这里只显示了两个,一个是任何用户都能看得到的红箭头标记了,上面解释了,另一个就是当前登入用户的权限(用的是admin用户 —就是管理员)
这里静态资源也出来了
当前是admin用户所以可以去管理员界面
我们再来看看TAO用户
图片文字有解释
//当前为TAO权限我们点击一下去管理员界面试一下
这个界面是权限拒接界面上面有提到denied.html