pom.xml
org.springframework
spring-webmvc
5.1.2.RELEASE
org.springframework.security
spring-security-web
5.1.2.RELEASE
org.springframework.security
spring-security-config
5.1.2.RELEASE
加入过滤器
- 选择一:web.xml
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
- 选择二:AbstractSecurityWebApplicationInitializer
package cn.johnyu;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer {
}
配置安全策略
- 使用配置文件,加入security的名称空间(暂时不可用)
- 使用配置类(继承
WebSecurityConfigurerAdapter
)
package cn.johnyu.security;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
*用来修改过滤器链
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**");
web.ignoring().antMatchers("/images/**");
web.ignoring().antMatchers("/js/**");
}
/**
*配置UserDetailService,完成"鉴权"
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.passwordEncoder(new StandardPasswordEncoder("53cr3t"));
}
/**
* 修改拦截器,保护请求
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/work")
.hasAuthority("ROLE_READER")
.antMatchers("/other")
.hasAnyRole("GUEST","READER")
.anyRequest().permitAll()
.and()
.formLogin()
.and()
.exceptionHandling()
.accessDeniedPage("/forbidden")
.and()
.logout()
.logoutUrl("/logout");
}
}
关于登录页
- 如果没有Override configure(HttpSecurity),则会提供一个默认的登录页(否则没有)
- 如果加入
.formLogin()
,则会找回该登录页 - 该页核心内容如下:
- 你可以加入自定义的登录页,方法如下:
.formLogin()
.loginPage("/login")
.failureUrl("/login?error=true");
此时,如果有以下控制器
@RequestMapping("/login")
public String login(){
return "login";
}
则会将页面转向到自定义的页面上。
关于退出
.and()
.logout()
.logoutUrl("/logout");
这将指定“/logout”为退出链接,退出后将回到“/login?logout”
关于role不符合
- 默认将返回403码
- 指定403页面的方法
.and()
.exceptionHandling()
.accessDeniedPage("/forbidden")
关于鉴权
- 可以使用
void configure(AuthenticationManagerBuilder)
进行 - 可以使用三种方法进行:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication();
auth.inMemoryAuthentication();
auth.ldapAuthentication();
}
- 无论哪种方式,都将采用hash方式处理密码
使用JDBC进行鉴权
- 直接使用(无定制方式)
private DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.passwordEncoder(new StandardPasswordEncoder("53cr3t"));//这个是必选项
}
- Security默认使用以下表的结构:schema.sql
-- ----------------------------
-- 用户表: username做为唯一字段
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int(8) NOT NULL AUTO_INCREMENT,
`username` varchar(20) DEFAULT NULL,
`password` varchar(100) DEFAULT NULL,
`enabled` tinyint(4) DEFAULT NULL,
PRIMARY KEY (`id`)
);
-- ----------------------------
-- 角色表:username外键关联users
-- 注意:它与用户表,存在着间接的 Many To Many 的关系,用户的角色可以来自:直接角色和所属组的角色
-- ----------------------------
DROP TABLE IF EXISTS `authorities`;
CREATE TABLE `authorities` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) DEFAULT NULL,
`authority` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
);
-- ----------------------------
-- 用户组:
-- 注意:它与users存在 Many to Many的关系,但与authorities,是Many To One 的关系
-- ----------------------------
DROP TABLE IF EXISTS `groups`;
CREATE TABLE `groups` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`groupname` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
);
-- ----------------------------
-- users 与 authorites 的一对一的关联表,group_Id外键关联groups表
-- ----------------------------
DROP TABLE IF EXISTS `group_authorities`;
CREATE TABLE `group_authorities` (
`group_Id` int(11) NOT NULL AUTO_INCREMENT,
`authority` varchar(50) DEFAULT NULL,
PRIMARY KEY (`group_Id`)
);
-- ----------------------------
-- 用户 与 用户组 之间的多到多关联关系(username外键关联users表,group_Id外键关联groups表)
-- ----------------------------
DROP TABLE IF EXISTS `group_members`;
CREATE TABLE `group_members` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) DEFAULT NULL,
`group_Id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
);
初如化的数据:data.sql
-- 密码为123(salt=53cr3t new StandardPasswordEncoder("53cr3t").encode("123"))
insert into users (username,password,enabled) values('john','97c1e5a28f4d983316d375550e1e28ee50b6d27b5c5a71d6daa4c694227a8d8d94e8e6917ca4ad13',1);
insert into users (username,password,enabled) values('tom','123',0);
insert into users (username,password,enabled) values('alice','221a34d7021d75c196b80cbeb0ce53818871ab2a27be7113afc1ee1d08ab157c19cdef4490a06080',1);
-- 必须确保User具有Role
insert into authorities (username,authority) values('john','ROLE_READER');
insert into authorities (username,authority) values('tom','ROLE_READER');
insert into authorities (username,authority) values('alice','ROLE_GUEST');
附:三条核心查询语句:
public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService, MessageSourceAware {
//用于用户身份认证
public static final String DEF_USERS_BY_USERNAME_QUERY = "select username,password,enabled from users where username = ?";
//查询用户的角色
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "select username,authority from authorities where username = ?";
//查询用户所属的组的角色
public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY = "select g.id, g.group_name, ga.authority from groups g, group_members gm, group_authorities ga where gm.username = ? and g.id = ga.group_id and g.id = gm.group_id";