目录
前言
实战开发:
一、Spring Security整合到SSM项目
1. pom文件引入包
2. web.xml 配置
3. 添加 spring-security.xml 文件
二、Spring Security实战应用
1. 项目结构
2. pom文件引入
3. web.xml 配置
4. Spring 配置 applicationContext.xml
5. spring-security.xml 配置
6. springmvc.xml 配置
7. 创建实体类
8. DAO层实现数据查询
9. SystemDao 接口编写(数据层接口类)
10. SystemService接口及实现类SystemServiceImpl编写
11. SystemController 控制器编写
12. SpringSecurity实战讲解
13. 运行项目查看效果
实战前提条件:
基础的SSM项目已集成完毕。在此基础上集成Spring Security实现web项目的安全保护 。
本文版本说明:
JDK:1.8
spring.version:5.2.12.RELEASE
Spring Security.version:4.2.5.RELEASE
Spring Security标签库:4.2.3.RELEASE
实战目标:
Authentication:认证,实现用户认证登录
Authorization:授权,设定用户的资源,访问权限。
org.springframework.security
spring-security-web
4.2.5.RELEASE
org.springframework.security
spring-security-config
4.2.5.RELEASE
org.springframework.security
spring-security-taglibs
4.2.3.RELEASE
注:本项目完整的pom文件稍后附上
注:先说说web.xml配置文件中Spring家族的加载顺序。
先启动spring ioc容器 --> 再启动spring-security --> 然后启动springmvc
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
Archetype Created Web Application
org.springframework.web.context.ContextLoaderListener
contextConfigLocation
classpath:spring/applicationContext.xml
classpath:spring/spring-security.xml
注:本项目完整的web.xml配置稍后附上
注:本文件使用form-login的方式进行认证,在项目resources文件下新建spring文件夹(如果没有的话)
以上为spring-security.xml 的基础配置,到此Spring Security整合到SSM项目中已经完毕!运行项目后,所有资源会被拦截,跳转到默认登录页要求用户进行登录认证后才能访问项目资源。如图:
4.0.0
com.wqbr
wqdemotwo
1.0-SNAPSHOT
war
wqdemotwo Maven Webapp
http://www.example.com
UTF-8
1.8
1.8
5.2.12.RELEASE
org.aspectj
aspectjweaver
1.6.8
org.springframework
spring-aop
${spring.version}
org.springframework
spring-context
${spring.version}
org.springframework
spring-web
${spring.version}
org.springframework
spring-webmvc
${spring.version}
org.springframework
spring-test
${spring.version}
org.springframework
spring-tx
${spring.version}
org.springframework
spring-jdbc
${spring.version}
org.springframework.security
spring-security-web
4.2.5.RELEASE
org.springframework.security
spring-security-config
4.2.5.RELEASE
org.springframework.security
spring-security-taglibs
4.2.3.RELEASE
com.fasterxml.jackson.core
jackson-databind
2.13.4
javax.servlet.jsp
jsp-api
2.0
provided
jstl
jstl
1.2
javax.servlet
servlet-api
2.5
provided
junit
junit
4.11
test
org.mybatis
mybatis
3.4.5
org.mybatis.generator
mybatis-generator-core
1.3.5
org.mybatis
mybatis-spring
2.0.7
com.oracle.database.jdbc
ojdbc8
21.3.0.0
cn.easyproject
orai18n
12.1.0.2.0
com.alibaba
druid
1.1.12
wqdemotwo
org.apache.maven.plugins
maven-source-plugin
2.1.1
attach-sources
jar-no-fork
true
org.apache.maven.plugins
maven-compiler-plugin
3.1
1.8
UTF-8
org.apache.maven.plugins
maven-war-plugin
2.6
true
配置web.xml,加载spring(applicationContext.xml --spring默认配置文件),加载spring-security,加载springmvc。
characterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
characterEncodingFilter
/*
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
contextConfigLocation
classpath:spring/applicationContext.xml
classpath:spring/spring-security.xml
Archetype Created Web Application
org.springframework.web.context.ContextLoaderListener
dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring/springmvc.xml
1
dispatcherServlet
/
指定扫描包,数据源,整合集成接管mybatis。
classpath:mapping/SystemDao.xml
注:重点在SysUser实体类。实现UserDetails接口 复写接口的方法进行实现,建立各方法的对应属性到用户表中(不一定全建对应属性)。
我们先来看下UserDetails接口类的源码:
红色标注的3项建立在用户实体领域类中,如下SysUser 用户实体类代码:
package com.wqbr.domain;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* 系统用户,封装用户数据,实现 UserDetails 接口
* @author lv
* @date 2024年1月11日
*/
public class SysUser implements UserDetails {
private static final long serialVersionUID = 1L;
private String id;
private String username; //从UserDetails的重写方法中返回
private String password; //从UserDetails的重写方法中返回
private Date addtime;
private boolean accountnonexpired; //账户是否过期,从UserDetails的重写方法中返回
private boolean accountnonlocked; //账户是否锁定,从UserDetails的重写方法中返回
private boolean credentialsnonexpired; //密码是否过期,从UserDetails的重写方法中返回
private boolean enabled; //账户是否可用,从UserDetails的重写方法中返回
// 储存用户拥有的所有权限
private List authorities = new ArrayList<>(); //从UserDetails的重写方法中返回
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public Date getAddtime() {
return addtime;
}
public void setAddtime(Date addtime) {
this.addtime = addtime;
}
// 返回用户权限,上面声明了权限集合对象 authorities
@Override
public Collection extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
public void setAuthorities(List authorities) {
this.authorities = authorities;
}
// 返回用户密码,上面声明了属性 password
@Override
public String getPassword() {
return password;
}
// 返回用户名,上面声明了属性 username
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return accountnonexpired;
}
public void setAccountnonexpired(boolean accountnonexpired) {
this.accountnonexpired = accountnonexpired;
}
@Override
public boolean isAccountNonLocked() {
return accountnonlocked;
}
public void setAccountnonlocked(boolean accountnonlocked) {
this.accountnonlocked = accountnonlocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsnonexpired;
}
public void setCredentialsnonexpired(boolean credentialsnonexpired) {
this.credentialsnonexpired = credentialsnonexpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
注:private Listauthorities = new ArrayList<>(); 此属性稍后赋值演示
角色(SysRole )和资源(SysPermission)实体类代码参见以下文章建立: spirng框架之spring security(二)insert 语句补充-CSDN博客https://blog.csdn.net/u011529483/article/details/135467110?spm=1001.2014.3001.5501
SystemDao.xml,mybatis的mapper文件(映射SQL语句)
package com.wqbr.persistence;
import com.wqbr.domain.SysPermission;
import com.wqbr.domain.SysUser;
import java.util.List;
public interface SystemDao {
/**
* 查询当前用户对象
*/
public SysUser findByUsername(String username);
/**
* 查询当前用户拥有的资源
*/
public List findPermissionByUsername(String username);
}
package com.wqbr.service;
import com.wqbr.domain.SysPermission;
import java.util.List;
/**
* 系统服务接口
* @author lv
* @date 2024年1月11日
*/
public interface SystemService {
/**
* 查询当前用户拥有的资源
*/
public List findPermissionByUsername(String username);
}
package com.wqbr.service.impl;
import com.wqbr.domain.SysPermission;
import com.wqbr.persistence.SystemDao;
import com.wqbr.service.SystemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 系统服务接口实现
* @author lv
* @date 2024年1月11日
*/
@Service
public class SystemServiceImpl implements SystemService {
@Autowired
private SystemDao systemDao;
@Override
public List findPermissionByUsername(String username) {
return systemDao.findPermissionByUsername(username);
}
}
package com.wqbr.controller;
import com.wqbr.domain.Menus;
import com.wqbr.domain.SysPermission;
import com.wqbr.domain.SysUser;
import com.wqbr.service.SystemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
/**
* 系统用户控制器
* @author lv
* @date 2024年1月11日
*/
@Controller
@RequestMapping("/system")
public class SystemController {
/**
* 自动装配SystemService接口
*/
@Autowired
private SystemService systemService;
/**
* 处理超链接发送出来的请求
* @param model
* @return
*/
@RequestMapping(path = "/hello")
public String sayHello(Model model){
System.out.println("入门方法执行了2...");
// 配置了视图解析器后,写法
return "main/index";
}
@RequestMapping(path = "/haa")
public String haa(Model model){
System.out.println("haa *****bb 2 999999999999999999...");
// 向模型中添加属性msg与值,可以在html页面中取出并渲染
//model.addAttribute("msg","hello,SpringMVC");
// 配置了视图解析器后,写法
return "main/index";
}
@RequestMapping(path = "/index")
public String index(){
System.out.println("index 页面进入......");
return "main/index";
}
@RequestMapping(path = "/list")
public String list(){
System.out.println("list方法进入......");
return "main/list";
}
@RequestMapping(path = "/add")
public String add(){
System.out.println("add方法进入......");
return "main/add";
}
@GetMapping("/findMenu")
public ModelAndView findMenus(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
ModelAndView model = new ModelAndView("main/menu");
SysUser user = (SysUser) authentication.getPrincipal();
String username=user.getUsername();
if(username!=null){
List listMenu = new ArrayList<>();
List pList = systemService.findPermissionByUsername(username);
System.out.println("=-----=大小为:"+pList.size());
for (SysPermission permission : pList) {
if (permission.getResource_type().equals("menu")) {
Menus menu = new Menus();
menu.setId(Long.parseLong(permission.getId()));
menu.setName(permission.getName());
menu.setParentId(Long.parseLong(permission.getParent_id()));
menu.setParentIds(Long.parseLong(permission.getParent_ids()));
menu.setUrl(permission.getUrl());
listMenu.add(menu);
}
}
//request.setAttribute("listMenus", listMenu);
model.addObject("listMenu",listMenu);
// for (Menus menus : listMenu) {
// if (menus.getParentId() == 10000) { //10000为数据库中的值
// System.out.println("==" + menus.getName() + "[" + menus.getUrl() + "]");
// for (Menus menusch : listMenu) {
// if (menus.getId() == menusch.getParentId()) {
// System.out.println("---------" + menusch.getName() + "[" + menusch.getUrl() + "]");
// }
// }
// }
// }
}
return model;
}
}
现在请查看之前配置的spring-security.xml文件。
如图 spring-security.xml 文件中给出了 security:form-login 的4个属性。并禁用了 csrf 。且指定了 error-page 的路径。所以需要编写 Controller 实现 login-page 及 error-page。
package com.wqbr.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 系统用户控制器
* @author lv
* @date 2024年1月16日
*/
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(){
System.out.println("初始 指定 进入登录页面!。。。。。。。。。。。。。。");
return "login";
}
/**
* 自定义用户访问权限不足的处理方式(需要编写controller返回权限不足的页面)
* @return
*/
@RequestMapping("/accessDeny")
public String accessDeny(){
System.out.println("自定义用户访问权限不足的处理方式(需要编写controller返回权限不足的页面)。。。。。。。。。。。。。。");
return "accessdeny";
}
}
package com.wqbr.service.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Service;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Service
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
// new 一个 jackson 的 对象
private ObjectMapper objectMapper = new ObjectMapper();
/**
* 此方法会在登录成功后进行回调
*
* @param authentication:表示认证成功后的信息
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
// 自己构造json字符串,返回给前端
Map result = new HashMap<>();
result.put("authStr", "success");
String json = objectMapper.writeValueAsString(result);
// 使用response设置响应头为JSON
httpServletResponse.setContentType("text/json;charset=utf-8");
// 回写数据
httpServletResponse.getWriter().write(json);
}
}
package com.wqbr.service.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Service;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Service
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
// new 一个 jackson 的 对象
private ObjectMapper objectMapper = new ObjectMapper();
/**
* 此方法会在登录失败后进行回调
*
* @param authenticationException:表示认证失败后的信息
*/
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authenticationException) throws IOException, ServletException {
// 自己构造json字符串,返回给前端
Map result = new HashMap<>();
result.put("authStr", "fail");
String json = objectMapper.writeValueAsString(result);
// 使用response设置响应头为JSON
httpServletResponse.setContentType("text/json;charset=utf-8");
// 回写数据
httpServletResponse.getWriter().write(json);
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="utf-8" %>
<%@ page isELIgnored="false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
登录页面
用户登录
MyUserDetailsService类实现UserDetailsService接口,重写loadUserByUsername方法,实现此方法(用户登录时调用此方法,通过用户输入的登录信息查找数据库用户表进行身份认证匹配)
package com.wqbr.service.impl;
import com.wqbr.domain.SysPermission;
import com.wqbr.domain.SysUser;
import com.wqbr.persistence.SystemDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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 java.util.List;
/**
* 系统用户控制器
* @author lv
* @date 2024年1月16日
*/
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private SystemDao systemDao;
/**
* loadUserByUsername:读取用户信息
* 返回值类型 UserDetails 是 SpringSecurity 用来封装用户数据的接口
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("-------loadUserByUsername方法加载中。。。。username:"+username);
/**
* name: 用户名
* password: 密码
* authorities: 定义权限名称
*/
// User user = new User("admin", "123456",
// AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_ALL"));
// return user;
SysUser user=systemDao.findByUsername(username);
System.out.println("user-====="+user);
//判断
if(user!=null){
System.out.println(user.getUsername()+"---====---"+user.getPassword()+"----"+user.getAddtime());
/* List permList=systemDao.findPermissionByUsername(user.getUsername());
StringBuffer sb = new StringBuffer();
for (SysPermission sysPermission : permList) {
sb.append(sysPermission.getUrl());
sb.append(",");
}
sb.delete(sb.length()-1,sb.length());*/
List list=AuthorityUtils.createAuthorityList("ROLE_USER","admin");
user.setAuthorities(list); //设置权限列表
return user;
}
throw new UsernameNotFoundException(username+"用户名不存在!");
}
}
到此spring-security讲解完毕,接下来补全几个测试页面
accessdeny.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
用户访问权限不足!。。。。。。。。。。。。。。。。。。。。。。。
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="utf-8" %>
<%@ page isELIgnored="false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
welcome!
welcome! index页面
add.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
add...! 内测页面
list.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
list...! 列表 显示
list...! || 列表 显示
登录
输入错误的用户名、密码
输入正确的用户名、密码
登录成功后尝试访问/system/add 和 /system/list 方法请求
如图 /system/add 成功访问,/system/list 无法访问,因为权限不足。如下图用户详情类中没有给用户指定 ROLE_ALL 权限
现在我们关闭浏览器,重新打开浏览器。不登录的情况下访问控制器的 /system/add 请求 和 /system/index 请求。
访问 http://localhost:8080/wqdemotwo_war/system/add
访问 http://localhost:8080/wqdemotwo_war/system/index
下一篇讲讲用户认证登录进来以后如何根据角色获取菜单资源