主要用到了springboot,springsecurity,mybatis,jsp
1. 創建項目
使用idea
中的spring
工具創建項目創建時勾選springboot start web
和springsecurity
最終生成的pom.xml
文件如下
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springboot_security_jsp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>springboot_security_jsp</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<!-- https://mvnrepository.com/artifact/tk.mybatis/mapper-spring-boot-starter -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
<!-- 整合jsp需要如下兩個包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>9.0.30</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. 數據庫表字段信息
CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `sys_permission` (
`ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '編號',
`permission_NAME` varchar(30) DEFAULT NULL COMMENT '菜單名稱',
`permission_url` varchar(100) DEFAULT NULL COMMENT '菜單地址',
`parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父菜單id',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `sys_role` (
`ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '編號',
`ROLE_NAME` varchar(30) DEFAULT NULL COMMENT '角色名稱',
`ROLE_DESC` varchar(60) DEFAULT NULL COMMENT '角色描述',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
CREATE TABLE `sys_role_permission` (
`RID` int(11) NOT NULL COMMENT '角色編號',
`PID` int(11) NOT NULL COMMENT '權限編號',
PRIMARY KEY (`RID`,`PID`),
KEY `FK_Reference_12` (`PID`),
CONSTRAINT `FK_Reference_11` FOREIGN KEY (`RID`) REFERENCES `sys_role` (`ID`),
CONSTRAINT `FK_Reference_12` FOREIGN KEY (`PID`) REFERENCES `sys_permission` (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL COMMENT '用戶名稱',
`password` varchar(120) NOT NULL COMMENT '密碼',
`status` int(1) DEFAULT '1' COMMENT '1開啟0關閉',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
CREATE TABLE `sys_user_role` (
`UID` int(11) NOT NULL COMMENT '用戶編號',
`RID` int(11) NOT NULL COMMENT '角色編號',
PRIMARY KEY (`UID`,`RID`),
KEY `FK_Reference_10` (`RID`),
CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `sys_role` (`ID`),
CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `sys_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3. 創建角色 pojo對象
這里直接使用SpringSecurity
的角色規范
package com.example.springboot_security_jsp.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
/**
* @author john
* @date 2020/1/11 - 20:12
*/
public class SysRole implements GrantedAuthority {
private Integer id;
private String roleName;
private String roleDesc;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getRoleDesc() {
return roleDesc;
}
public void setRoleDesc(String roleDesc) {
this.roleDesc = roleDesc;
}
//標記此屬性不做json處理
@JsonIgnore
@Override
public String getAuthority() {
return roleName;
}
}
4. 創建用戶pojo
package com.example.springboot_security_jsp.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author john
* @date 2020/1/11 - 20:15
*/
public class SysUser implements UserDetails {
private Integer id;
private String username;
private String password;
private Integer status;
private List<SysRole> roles = new ArrayList<>();
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 Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public List<SysRole> getRoles() {
return roles;
}
public void setRoles(List<SysRole> roles) {
this.roles = roles;
}
@JsonIgnore
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
}
5. 配置application.yml
server:
port: 8082
spring:
mvc:
view:
suffix: .jsp
prefix: /pages/
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///test
username: root
password: root
mybatis:
type-aliases-package: com.example.springboot_security_jsp.domain
configuration:
map-underscore-to-camel-case: true
logging:
level:
com.demo: debug
6. 編寫RoleMapper
package com.example.springboot_security_jsp.mapper;
import com.example.springboot_security_jsp.domain.SysRole;
import org.apache.ibatis.annotations.Select;
import tk.mybatis.mapper.common.Mapper;
import java.util.List;
/**
* @author john
* @date 2020/1/11 - 20:20
*/
public interface RoleMapper extends Mapper<SysRole> {
@Select("select r.id,r.role_name roleName ,r.role_desc roleDesc " +
"FROM sys_role r,sys_user_role ur " +
"WHERE r.id=ur.rid AND ur.uid=#{uid}")
public List<SysRole> findByUid(Integer uid);
}
7. 編寫UserMapper
package com.example.springboot_security_jsp.mapper;
import com.example.springboot_security_jsp.domain.SysUser;
import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import tk.mybatis.mapper.common.Mapper;
import java.util.List;
/**
* @author john
* @date 2020/1/11 - 20:25
*/
public interface UserMapper extends Mapper<SysUser> {
@Select("select * from sys_user where username=#{username}")
@Results({
@Result(id = true, property = "id", column = "id"),
@Result(property = "roles", column = "id", javaType = List.class,
many = @Many(select = "com.example.springboot_security_jsp.mapper.RoleMapper.findByUid"))
})
public SysUser findByUsername(String username);
}
8. 編寫UserService
以及其實現類
package com.example.springboot_security_jsp.service;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* @author john
* @date 2020/1/11 - 20:31
*/
public interface UserService extends UserDetailsService {
}
package com.example.springboot_security_jsp.service.impl;
import com.example.springboot_security_jsp.mapper.UserMapper;
import com.example.springboot_security_jsp.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author john
* @date 2020/1/11 - 20:32
*/
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userMapper.findByUsername(username);
}
}
9. SpringSecurity
配置類
package com.example.springboot_security_jsp.config;
import com.example.springboot_security_jsp.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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 org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* @author john
* @date 2020/1/11 - 19:17
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 認證用戶的來源(內存或者數據庫)
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
}
//配置springsecurity相關信息
protected void configure(HttpSecurity http) throws Exception {
// 釋放靜態資源,指定資源攔截規則,指定自定義認證頁面,指定退出認證配置,csrf配置
http.authorizeRequests()
.antMatchers("/login.jsp", "/failer.jsp", "/css/**", "/img/**", "/plugins/**")
.permitAll()
.antMatchers("/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.jsp")
.loginProcessingUrl("/login")
.successForwardUrl("/index.jsp")
.failureForwardUrl("/failer.jsp")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login.jsp")
.invalidateHttpSession(true)
.permitAll();
//禁用csrf
// .and()
// .csrf()
// .disable();
}
}
10. 編寫啟動類
package com.example.springboot_security_jsp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.example.springboot_security_jsp.mapper")
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringbootSecurityJspApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSecurityJspApplication.class, args);
}
}
11. 編寫一個控制器用於訪問
package com.example.springboot_security_jsp.controller;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author john
* @date 2020/1/11 - 19:07
*/
@Controller
@RequestMapping("/product")
public class ProductController {
@RequestMapping("/findAll")
@Secured("ROLE_PRODUCT")
public String hello() {
return "product-list";
}
}
12. 配置異常攔截器
package com.example.springboot_security_jsp.controller.advice;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
/**
* @author john
* @date 2020/1/11 - 20:40
*/
@ControllerAdvice
public class HandleControllerException {
@ExceptionHandler(RuntimeException.class)
public String exceptionHandler(RuntimeException e) {
if (e instanceof AccessDeniedException) {
//如果是權限不足異常,則跳轉到權限不足頁面!
return "redirect:/403.jsp";
}
//其余的異常都到500頁面!
return "redirect:/500.jsp";
}
}
13. 測試
jsp等靜態頁面資源查看碼雲代碼
SpringBoot官方是不推薦在SpringBoot中使用jsp的,需要導入tomcat插件啟動項目,不能再用SpringBoot默認tomcat了
在maven中執行
mvn spring-boot:run
打開瀏覽器訪問<http://localhost:8082/login.jsp>
輸入用戶名john
密碼123
登錄成功
當訪問權限不足的頁面會顯示自定義頁面
正常訪問其他頁面
14. 代碼地址
15. 知識點備忘
1. Spring Security主要jar包功能介紹
Spring Security主要jar包功能介紹
spring-security-core.jar
核心包,任何Spring Security功能都需要此包。
spring-security-web.jar
web工程必備,包含過濾器和相關的Web安全基礎結構代碼。
spring-security-config.jar
用於解析xml配置文件,用到Spring Security的xml配置文件的就要用到此包。
spring-security-taglibs.jar
Spring Security提供的動態標簽庫,jsp頁面可以用
2. Spring Security 常用過濾器介紹
1. org.springframework.security.web.context.SecurityContextPersistenceFilter
首當其沖的一個過濾器,作用之重要,自不必多言。
SecurityContextPersistenceFilter
主要是使用SecurityContextRepository
在session
中保存或更新一個SecurityContext
,並將SecurityContext
給以后的過濾器使用,來為后續filter建立所需的上下文。
SecurityContext
中存儲了當前用戶的認證以及權限信息。
2. org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
此過濾器用於集成SecurityContext
到Spring
異步執行機制中的WebAsyncManager
3 . org.springframework.security.web.header.HeaderWriterFilter
向請求的Header
中添加相應的信息,可在http
標簽內部使用security:headers
來控制
4 . org.springframework.security.web.csrf.CsrfFilter
csrf
又稱跨域請求偽造,SpringSecurity
會對所有post
請求驗證是否包含系統生成的csrf
的token
信息,
如果不包含,則報錯。起到防止csrf
攻擊的效果。
5. org.springframework.security.web.authentication.logout.LogoutFilter
匹配 URL
為/logout
的請求,實現用戶退出,清除認證信息。
6 . org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
認證操作全靠這個過濾器,默認匹配URL
為/login
且必須為POST
請求。
7 . org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
如果沒有在配置文件中指定認證頁面,則由該過濾器生成一個默認認證頁面。
8 . org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
由此過濾器可以生產一個默認的退出登錄頁面
9 . org.springframework.security.web.authentication.www.BasicAuthenticationFilter
此過濾器會自動解析HTT
P請求中頭部名字為Authentication
,且以Basic
開頭的頭信息。
10 . org.springframework.security.web.savedrequest.RequestCacheAwareFilter
通過HttpSessionRequestCache
內部維護了一個RequestCache
,用於緩存HttpServletRequest
11 . org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
針對ServletRequest
進行了一次包裝,使得request
具有更加豐富的API
12 . org.springframework.security.web.authentication.AnonymousAuthenticationFilter
當SecurityContextHolder
中認證信息為空,則會創建一個匿名用戶存入到SecurityContextHolder
中。
spring security
為了兼容未登錄的訪問,也走了一套認證流程,只不過是一個匿名的身份。
13 . org.springframework.security.web.session.SessionManagementFilter
SecurityContextRepository
限制同一用戶開啟多個會話的數量
14 . org.springframework.security.web.access.ExceptionTranslationFilter
異常轉換過濾器位於整個springSecurityFilterChain
的后方,用來轉換整個鏈路中出現的異常
15 . org.springframework.security.web.access.intercept.FilterSecurityInterceptor
獲取所配置資源訪問的授權信息,根據SecurityContextHolder
中存儲的用戶信息來決定其是否有權限。
3. csrf處理
1. 禁用csrf,在配置文件編寫(不推薦)
2. 在認證頁面攜帶token請求
4. 退出登錄
注意:一旦開啟了csrf
防護功能,logout
處理器便只支持POST
請求方式了!
<form action="${pageContext.request.contextPath}/logout" method="post">
<security:csrfInput/>
<input type="submit" value="注銷">
</form>
5. remember me頁面
<!-- 注意name和value屬性的值不要寫錯哦 -->
<input type="checkbox" name="remember-me" value="true">
6. 持久化remember me信息
創建一張表,注意這張表的名稱和字段都是固定的,不要修改。
CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL,
PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
在代碼中如下配置
@Autowired
private DataSource dataSource;
// 1. 配置TokenRepository
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
//自動創建上面的表
tokenRepository.setCreateTableOnStartup(true);
return tokenRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 表單登錄
http
//記住我的配置
// rememberMe需要的配置包含TokenRepository對象以及token過期時間
.rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60 * 60 * 24)
.userDetailsService(userDetailsService);
}
7. 在頁面中顯示當前認證用戶名
<span class="hidden-xs">
<security:authentication property="principal.username" />
</span>
或者
<span class="hidden-xs">
<security:authentication property="name" />
</span>
8. 在頁面中控制連接是否顯示
<%-- 管理員和商品管理權限可以--%>
<security:authorize access="hasAnyRole('ROLE_PRODUCT','ROLE_ADMIN')">
<li id="system-setting"><a
href="${pageContext.request.contextPath}/product/findAll">
<i class="fa fa-circle-o"></i> 產品管理
</a></li>
</security:authorize>
<%-- 管理員和訂單權限可以--%>
<security:authorize access="hasAnyRole('ROLE_ORDER','ROLE_ADMIN')">
<li id="system-setting"><a
href="${pageContext.request.contextPath}/order/findAll">
<i class="fa fa-circle-o"></i> 訂單管理
</a></li>
</security:authorize>
9. 授權操作
SpringSecurity
可以通過注解的方式來控制類或者方法的訪問權限。
1. securedEnabled
securedEnabled
是SpringSecurity
提供的注解
在入口處配置
@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringbootSecurityJspApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSecurityJspApplication.class, args);
}
}
在controller
處寫
@Controller
@RequestMapping("/product")
public class ProductController {
@Secured({"ROLE_PRODUCT","ROLE_ADMIN"}) //springsecurity內部的權限控制注解開關
@RequestMapping("/findAll")
public String findAll(){
return "product-list";
}
}
2. jsr250注解
在入口處配置
@SpringBootApplication
//表示支持jsr250-api的注解,需要jsr250-api的jar包
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class SpringbootSecurityJspApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSecurityJspApplication.class, args);
}
}
在controller
處寫
@Controller
@RequestMapping("/product")
public class ProductController {
@RolesAllowed({"ROLE_PRODUCT", "ROLE_ADMIN"}) //jsr250注解
@RequestMapping("/findAll")
public String findAll() {
return "product-list";
}
}
3. prePostEnabled
在入口處配置
@SpringBootApplication
//表示支持spring表達式注解
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringbootSecurityJspApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSecurityJspApplication.class, args);
}
}
在controller
處寫(如下兩種方式)
@Controller
@RequestMapping("/product")
public class ProductController {
//SpringSecurity提供的注解
// @PreAuthorize("hasAnyRole('ROLE_PRODUCT','ROLE_ADMIN')")
@PreAuthorize("hasAnyAuthority('ROLE_PRODUCT','ROLE_ADMIN')")
@RequestMapping("/findAll")
public String findAll(){
return "product-list";
}
}
以上三種選一種即可
10. 編寫異常處理器處理異常
@ControllerAdvice
public class ControllerExceptionAdvice {
//只有出現AccessDeniedException異常才調轉403.jsp頁面
@ExceptionHandler(AccessDeniedException.class)
public String exceptionAdvice(){
return "forward:/403.jsp";
}
}