项目security_simple(认证授权项目)
1.新建springboot项目
这儿选择springboot版本我选择的是2.0.6
点击finish后完成项目的创建
2.引入maven依赖 下面是我引入的依赖
1 xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 <modelVersion>4.0.0modelVersion> 5 6 <groupId>com.megalithgroupId> 7 <artifactId>security_simpleartifactId> 8 <version>0.0.1-SNAPSHOTversion> 9 <packaging>jarpackaging> 10 11 <name>security_simplename> 12 <description>Demo project for Spring Bootdescription> 13 14 <parent> 15 <groupId>org.springframework.bootgroupId> 16 <artifactId>spring-boot-starter-parentartifactId> 17 <version>2.0.6.RELEASEversion> 18 <relativePath/> 19 parent> 20 21 <properties> 22 <project.build.sourceEncoding>UTF-8project.build.sourceEncoding> 23 <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding> 24 <java.version>1.8java.version> 25 <pagehelper.version>1.1.0pagehelper.version> 26 <mybatis.generator.version>1.3.2mybatis.generator.version> 27 <fastjson.version>1.2.31fastjson.version> 28 <jackson.version>2.9.7jackson.version> 29 properties> 30 31 <dependencies> 32 <dependency> 33 <groupId>junitgroupId> 34 <artifactId>junitartifactId> 35 <version>4.11version> 36 <scope>testscope> 37 dependency> 38 39 <dependency> 40 <groupId>org.springframework.bootgroupId> 41 <artifactId>spring-boot-starter-webartifactId> 42 <exclusions> 43 <exclusion> 44 <groupId>org.springframework.bootgroupId> 45 <artifactId>spring-boot-starter-tomcatartifactId> 46 exclusion> 47 exclusions> 48 dependency> 49 <dependency> 50 <groupId>org.springframework.bootgroupId> 51 <artifactId>spring-boot-configuration-processorartifactId> 52 <optional>trueoptional> 53 dependency> 54 <dependency> 55 <groupId>org.springframework.bootgroupId> 56 <artifactId>spring-boot-starter-jettyartifactId> 57 dependency> 58 59 60 61 <dependency> 62 <groupId>org.springframework.bootgroupId> 63 <artifactId>spring-boot-starter-aopartifactId> 64 dependency> 65 66 <dependency> 67 <groupId>com.alibabagroupId> 68 <artifactId>fastjsonartifactId> 69 <version>${fastjson.version}version> 70 dependency> 71 72 73 <dependency> 74 <groupId>com.fasterxml.jackson.coregroupId> 75 <artifactId>jackson-coreartifactId> 76 <version>${jackson.version}version> 77 dependency> 78 <dependency> 79 <groupId>com.fasterxml.jackson.coregroupId> 80 <artifactId>jackson-databindartifactId> 81 <version>${jackson.version}version> 82 dependency> 83 <dependency> 84 <groupId>com.fasterxml.jackson.coregroupId> 85 <artifactId>jackson-annotationsartifactId> 86 <version>${jackson.version}version> 87 dependency> 88 <dependency> 89 <groupId>com.fasterxml.jackson.modulegroupId> 90 <artifactId>jackson-module-jaxb-annotationsartifactId> 91 <version>${jackson.version}version> 92 dependency> 93 94 95 <dependency> 96 <groupId>org.apache.commonsgroupId> 97 <artifactId>commons-lang3artifactId> 98 <version>3.3.2version> 99 dependency> 100 <dependency> 101 <groupId>com.fasterxml.uuidgroupId> 102 <artifactId>java-uuid-generatorartifactId> 103 <version>3.1.3version> 104 dependency> 105 106 107 108 <dependency> 109 <groupId>com.alibabagroupId> 110 <artifactId>druidartifactId> 111 <version>1.0.27version> 112 dependency> 113 114 <dependency> 115 <groupId>org.mybatis.spring.bootgroupId> 116 <artifactId>mybatis-spring-boot-starterartifactId> 117 <version>1.3.0version> 118 dependency> 119 120 <dependency> 121 <groupId>org.mybatis.generatorgroupId> 122 <artifactId>mybatis-generator-coreartifactId> 123 <version>${mybatis.generator.version}version> 124 dependency> 125 126 <dependency> 127 <groupId>org.springframework.bootgroupId> 128 <artifactId>spring-boot-starter-validationartifactId> 129 dependency> 130 131 132 <dependency> 133 <groupId>mysqlgroupId> 134 <artifactId>mysql-connector-javaartifactId> 135 <scope>runtimescope> 136 dependency> 137 138 <dependency> 139 <groupId>org.springframework.bootgroupId> 140 <artifactId>spring-boot-devtoolsartifactId> 141 <optional>trueoptional> 142 dependency> 143 144 <dependency> 145 <groupId>org.springframework.bootgroupId> 146 <artifactId>spring-boot-starter-testartifactId> 147 <scope>testscope> 148 dependency> 149 150 151 <dependency> 152 <groupId>org.springframework.bootgroupId> 153 <artifactId>spring-boot-starter-data-jpaartifactId> 154 dependency> 155 156 157 <dependency> 158 <groupId>org.springframework.bootgroupId> 159 <artifactId>spring-boot-starter-securityartifactId> 160 dependency> 161 <dependency> 162 <groupId>org.springframework.security.oauth.bootgroupId> 163 <artifactId>spring-security-oauth2-autoconfigureartifactId> 164 <version>2.0.6.RELEASEversion> 165 dependency> 166 167 dependencies> 168 169 <build> 170 <plugins> 171 <plugin> 172 <groupId>org.springframework.bootgroupId> 173 <artifactId>spring-boot-maven-pluginartifactId> 174 plugin> 175 plugins> 176 build> 177 178 179 project>
3.新建数据库(因为本项目采用jdbc的形式存储token相关)
建表sql语句的地址为 https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql 这儿记得不同的数据库中的特殊字段需要修改一下,建好的表如下
4.进行配置
4.1 CustomAuthorizationServerConfig 类
1 import org.springframework.beans.factory.annotation.Autowired; 2 import org.springframework.context.annotation.Bean; 3 import org.springframework.context.annotation.Configuration; 4 import org.springframework.http.HttpHeaders; 5 import org.springframework.security.authentication.AuthenticationManager; 6 import org.springframework.security.core.userdetails.UserDetailsService; 7 import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; 8 import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; 9 import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; 10 import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; 11 import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; 12 import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; 13 import org.springframework.security.oauth2.provider.token.TokenStore; 14 import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; 15 import org.springframework.web.cors.CorsConfiguration; 16 import org.springframework.web.cors.CorsConfigurationSource; 17 import org.springframework.web.filter.CorsFilter; 18 19 import javax.annotation.Resource; 20 import javax.servlet.http.HttpServletRequest; 21 import javax.sql.DataSource; 22 23 /** 24 * @Description: 配置授权认证服务类 25 * @author: zhoum 26 * @Date: 2018-11-22 27 * @Time: 13:41 28 */ 29 @Configuration 30 @EnableAuthorizationServer //授权认证中心 31 public class CustomAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { 32 33 /** 34 * 权限验证控制器 35 */ 36 @Autowired 37 private AuthenticationManager authenticationManager; 38 /** 39 * 数据源,保存token的时候需要 默认为spring中配置的datasource 40 */ 41 @Autowired 42 private DataSource dataSource; 43 /** 44 * 设置保存token的方式,一共有五种,这里采用数据库的方式 45 */ 46 @Autowired 47 private TokenStore tokenStore; 48 49 @Bean 50 public TokenStore tokenStore() { 51 return new JdbcTokenStore(dataSource); 52 } 53 54 /** 55 * 自定义登录或者鉴权失败时的返回信息 56 */ 57 @Resource(name = "webResponseExceptionTranslator") 58 private WebResponseExceptionTranslator webResponseExceptionTranslator; 59 60 61 /******************配置区域**********************/ 62 63 64 /** 65 * 用来配置授权(authorizatio)以及令牌(token)的访问端点和令牌服务 核心配置 在启动时就会进行配置 66 */ 67 @Override 68 public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { 69 //开启密码授权类型 70 endpoints.authenticationManager(authenticationManager); 71 //配置token存储方式 72 endpoints.tokenStore(tokenStore); 73 //自定义登录或者鉴权失败时的返回信息 74 endpoints.exceptionTranslator(webResponseExceptionTranslator); 75 } 76 77 /** 78 * 用来配置令牌端点(Token Endpoint)的安全约束. 79 */ 80 @Override 81 public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { 82 /** 83 * 配置oauth2服务跨域 84 */ 85 CorsConfigurationSource source = new CorsConfigurationSource() { 86 @Override 87 public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { 88 CorsConfiguration corsConfiguration = new CorsConfiguration(); 89 corsConfiguration.addAllowedHeader("*"); 90 corsConfiguration.addAllowedOrigin(request.getHeader(HttpHeaders.ORIGIN)); 91 corsConfiguration.addAllowedMethod("*"); 92 corsConfiguration.setAllowCredentials(true); 93 corsConfiguration.setMaxAge(3600L); 94 return corsConfiguration; 95 } 96 }; 97 98 security.tokenKeyAccess("permitAll()") 99 .checkTokenAccess("permitAll()") 100 .allowFormAuthenticationForClients() 101 .addTokenEndpointAuthenticationFilter(new CorsFilter(source)); 102 } 103 104 /** 105 * 用来配置客户端详情服务(ClientDetailsService), 106 * 客户端详情信息在这里进行初始化, 数据库在进行client_id 与 client_secret验证时 会使用这个service进行验证 107 */ 108 @Override 109 public void configure(ClientDetailsServiceConfigurer clients) throws Exception { 110 clients.jdbc(dataSource); 111 } 112 }
注意: 1. authenticationManager 的配置类会在后面的WebSecurityAdaptConfig配置中给出
2.TokenStore 一共有五种配置方式 本项目采用jdbc也就是数据库的形式
3.WebResponseExceptionTranslator为自定义的验证错误异常返回类,代码如下,其中responsedata为自定义的数据返回类 包含code,message,data三个属性
1 import cn.com.megalith.common.ResponseData; 2 import org.springframework.context.annotation.Bean; 3 import org.springframework.context.annotation.Configuration; 4 import org.springframework.http.HttpHeaders; 5 import org.springframework.http.HttpStatus; 6 import org.springframework.http.ResponseEntity; 7 import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; 8 import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator; 9 import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; 10 11 /** 12 * @Description: web全局异常返回处理器 13 * @author: zhoum 14 * @Date: 2018-11-22 15 * @Time: 14:48 16 */ 17 @Configuration 18 public class WebResponseExceptionTranslateConfig { 19 /** 20 * 自定义登录或者鉴权失败时的返回信息 21 */ 22 @Bean(name = "webResponseExceptionTranslator") 23 public WebResponseExceptionTranslator webResponseExceptionTranslator() { 24 return new DefaultWebResponseExceptionTranslator() { 25 @Override 26 public ResponseEntity translate(Exception e) throws Exception { 27 ResponseEntity responseEntity = super.translate(e); 28 OAuth2Exception body = (OAuth2Exception) responseEntity.getBody(); 29 HttpHeaders headers = new HttpHeaders(); 30 headers.setAll(responseEntity.getHeaders().toSingleValueMap()); 31 // do something with header or response 32 if ( 400 == responseEntity.getStatusCode().value() ) { 33 System.out.println(body.getMessage()); 34 if ( "Bad credentials".equals(body.getMessage()) ) { 35 return new ResponseEntity(new ResponseData("400" , "您输入的用户名或密码错误" , null) , headers , HttpStatus.OK); 36 } 37 } 38 return new ResponseEntity(body , headers , responseEntity.getStatusCode()); 39 40 } 41 }; 42 } 43 }
4.2ResourceServerConfig
1 import org.springframework.context.annotation.Configuration; 2 import org.springframework.security.config.annotation.web.builders.HttpSecurity; 3 import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 4 import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; 5 6 /** 7 * @Description: 资源提供端的配置 8 * @author: zhoum 9 * @Date: 2018-11-22 10 * @Time: 16:58 11 */ 12 @Configuration 13 @EnableResourceServer //开启资源提供服务的配置 是默认情况下spring security oauth2的http配置 会被WebSecurityConfigurerAdapter的配置覆盖 14 public class ResourceServerConfig extends ResourceServerConfigurerAdapter { 15 16 @Override 17 public void configure(HttpSecurity http) throws Exception { 18 http 19 .authorizeRequests() 20 .antMatchers("/**").permitAll(); 21 } 22 }
这儿注意,后面的WebSecurityAdaptConfig的配置类会覆盖这儿的配置
4.3 WebSecurityAdaptConfig 配置类
1 import cn.com.megalith.auth.CustomerAuthenticationProvider; 2 import org.springframework.beans.factory.annotation.Autowired; 3 import org.springframework.context.annotation.Bean; 4 import org.springframework.context.annotation.Configuration; 5 import org.springframework.security.authentication.AuthenticationManager; 6 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 7 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 8 import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 10 import org.springframework.security.core.userdetails.UserDetailsService; 11 import org.springframework.security.crypto.password.PasswordEncoder; 12 13 import javax.annotation.Resource; 14 15 /** 16 * @Description: //是默认情况下spring security的http配置 优于ResourceServerConfigurerAdapter的配置 17 * @author: zhoum 18 * @Date: 2018-11-22 19 * @Time: 17:06 20 */ 21 @Configuration //开启三种可以在方法上面加权限控制的注解 22 @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) 23 public class WebSecurityAdaptConfig extends WebSecurityConfigurerAdapter { 24 25 /** 26 * 获取用户的验证配置类 27 */ 28 @Resource(name = "signUserDetailService") 29 private UserDetailsService userDetailsService; 30 /** 31 * 加密配置 32 */ 33 @Autowired 34 private PasswordEncoder passwordEncoder; 35 36 /** 37 * 密码验证处理器 38 */ 39 @Resource(name = "myCustomerAuthenticationProvider") 40 private CustomerAuthenticationProvider customerAuthenticationProvider; 41 42 /** 43 * spring security设置 44 */ 45 @Override 46 protected void configure(HttpSecurity http) throws Exception { 47 http 48 .authorizeRequests()//定义哪些url需要被保护 哪些不需要保护 49 .antMatchers("/oauth/token" , "oauth/check_token").permitAll()//定义这两个链接不需要登录可访问 50 .antMatchers("/**").permitAll() //定义所有的都不需要登录 目前是测试需要 51 .anyRequest().authenticated() //其他的都需要登录 52 //.antMatchers("/sys/**").hasRole("admin")///sys/**下的请求 需要有admin的角色 53 .and() 54 .formLogin().loginPage("/login")//如果未登录则跳转登录的页面 这儿可以控制登录成功和登录失败跳转的页面 55 .usernameParameter("username").passwordParameter("password").permitAll()//定义号码与密码的parameter 56 .and() 57 .csrf().disable();//防止跨站请求 spring security中默认开启 58 59 60 } 61 62 /** 63 * 权限管理器 AuthorizationServerConfigurerAdapter认证中心需要的AuthenticationManager需要 64 */ 65 @Override 66 protected void configure(AuthenticationManagerBuilder auth) throws Exception { 67 //目的是为了前端获取数据时获取到整个form-data的数据,提供验证器 68 auth.authenticationProvider(customerAuthenticationProvider); 69 //配置登录user验证处理器 以及密码加密器 好让认证中心进行验证 70 auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); 71 } 72 73 /** 74 * 需要配置这个支持password模式 75 * support password grant type 76 * 77 * @return 78 * @throws Exception 79 */ 80 @Override 81 @Bean 82 public AuthenticationManager authenticationManagerBean() throws Exception { 83 return authenticationManager(); 84 } 85 }
注意: 1. UserDetailsService接口中主要包含有一个UserDetails loadUserByUsername(String var1) 方法,其中var1代表username即登录时输入的用户名,本项目的配置如下
1 import cn.com.megalith.auth.UserDetail; 2 import cn.com.megalith.domain.entity.User; 3 import cn.com.megalith.service.IUserService; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.security.core.userdetails.UserDetails; 6 import org.springframework.security.core.userdetails.UserDetailsService; 7 import org.springframework.security.core.userdetails.UsernameNotFoundException; 8 import org.springframework.stereotype.Component; 9 10 /** 11 * @Description: 12 * @author: zhoum 13 * @Date: 2018-11-22 14 * @Time: 15:07 15 */ 16 @Component("signUserDetailService") 17 public class SignUserDetaiServiceConfig implements UserDetailsService { 18 19 @Autowired 20 private IUserService userService; 21 22 /** 23 * 启动刷新token授权类型,会判断用户是否还是存活的 24 */ 25 @Override 26 public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { 27 User currentUser = userService.getByName(s); 28 if ( currentUser == null ) { 29 throw new UsernameNotFoundException("用户没用找到"); 30 } 31 32 return new UserDetail(currentUser); 33 } 34 }
这儿需要返回一个UserDetails的实现类用于spring security中验证 本项目的配置如下 其中的user为自定义的user实体
1 import cn.com.megalith.domain.entity.User; 2 import org.springframework.context.ApplicationContext; 3 import org.springframework.security.core.GrantedAuthority; 4 import org.springframework.security.core.authority.SimpleGrantedAuthority; 5 import org.springframework.security.core.userdetails.UserDetails; 6 7 import java.util.ArrayList; 8 import java.util.Collection; 9 import java.util.List; 10 11 /** 12 * @Description: 用户信息的实体类 13 * @author: zhoum 14 * @Date: 2018-11-22 15 * @Time: 15:46 16 */ 17 public class UserDetail implements UserDetails { 18 19 private User user; 20 21 private String id; 22 23 /** 24 * 通过构造方法在UserDetailsService的方法中将查到的user注入进去 25 */ 26 public UserDetail(User user) { 27 this.user = user; 28 if ( user != null ) { 29 this.id = user.getId(); 30 } 31 } 32 /** 33 * 对当前的用户赋予其应有的权限 34 */ 35 @Override 36 public Collection extends GrantedAuthority> getAuthorities() { 37 //添加权限 这儿暂时写死 应该从数据库中拿到 38 List authorisList = new ArrayList(); 39 authorisList.add(new SimpleGrantedAuthority("ROLE_AA")); 40 authorisList.add(new SimpleGrantedAuthority("ROLE_BB")); 41 authorisList.add(new SimpleGrantedAuthority("ROLE_CC")); 42 authorisList.add(new SimpleGrantedAuthority("ROLE_DD")); 43 return authorisList; 44 } 45 /** 46 * 获取密码 47 */ 48 @Override 49 public String getPassword() { 50 return user.getPassword(); 51 } 52 /** 53 * 获取用户名 54 */ 55 @Override 56 public String getUsername() { 57 return user.getUsername(); 58 } 59 /** 60 * 账户是否未过期 61 */ 62 @Override 63 public boolean isAccountNonExpired() { 64 return true; 65 } 66 /** 67 * 账户是否未锁定 68 */ 69 @Override 70 public boolean isAccountNonLocked() { 71 return true; 72 } 73 /** 74 * 证书是否未过期 75 */ 76 @Override 77 public boolean isCredentialsNonExpired() { 78 return true; 79 } 80 81 /** 82 * 是否有效 可对应数据库中的delete_flag字段 83 */ 84 @Override 85 public boolean isEnabled() { 86 return true; 87 } 88 89 public User getUser() { 90 return user; 91 } 92 93 public void setUser(User user) { 94 this.user = user; 95 } 96 97 public String getId() { 98 return id; 99 } 100 101 public void setId(String id) { 102 this.id = id; 103 } 104 }
注意: 2. PasswordEncoder 主要为加密管理器 用于密码的加密匹配 本项目的配置如下
1 import org.springframework.context.annotation.Configuration; 2 import org.springframework.security.crypto.password.PasswordEncoder; 3 import org.springframework.stereotype.Component; 4 5 import java.security.MessageDigest; 6 7 /** 8 * @Description:MD5加密 9 * @author: zhoum 10 * @Date: 2018-11-22 11 * @Time: 17:36 12 */ 13 @Component 14 public class EncodePassword implements PasswordEncoder { 15 16 /** 17 * md5加密 18 */ 19 @Override 20 public String encode(CharSequence charSequence) { 21 22 return MD5(charSequence.toString()); 23 } 24 25 /** 26 * 匹配规则 27 */ 28 @Override 29 public boolean matches(CharSequence charSequence , String s) { 30 return MD5(charSequence.toString()).equals(s); 31 } 32 33 /** 34 * md5加密过程 35 */ 36 public static String MD5(String key) { 37 char hexDigits[] = { 38 '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' 39 }; 40 try { 41 byte[] btInput = key.getBytes(); 42 // 获得MD5摘要算法的 MessageDigest 对象 43 MessageDigest mdInst = MessageDigest.getInstance("MD5"); 44 // 使用指定的字节更新摘要 45 mdInst.update(btInput); 46 // 获得密文 47 byte[] md = mdInst.digest(); 48 // 把密文转换成十六进制的字符串形式 49 int j = md.length; 50 char str[] = new char[ j * 2 ]; 51 int k = 0; 52 for (int i = 0; i < j; i++) { 53 byte byte0 = md[ i ]; 54 str[ k++ ] = hexDigits[ byte0 >>> 4 & 0xf ]; 55 str[ k++ ] = hexDigits[ byte0 & 0xf ]; 56 } 57 return new String(str); 58 } catch (Exception e) { 59 return null; 60 } 61 } 62 }
注意: 3.CustomerAuthenticationProvider 主要是为AuthenticationProvider借接口的实现类 为spring security提供密码验证器 是核心配置,这儿需要注意 系统中已经有相应的实现类,如果不配置,则系统中会默认使用org.springframework.security.authentication.dao.DaoAuthenticationProvider这个类来进行验证,DaoAuthenticationProvider这个类继承了org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider这个抽象类,所以我们要自定义provider验证流程可以实现AuthenticationProvider接口或者继承AbstractUserDetailsAuthenticationProvider抽象类均可,下面是两种模式的代码
(1)实现AuthenticationProvider形式 自己写验证流程
1 import org.slf4j.Logger; 2 import org.slf4j.LoggerFactory; 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.security.authentication.AuthenticationProvider; 5 import org.springframework.security.authentication.BadCredentialsException; 6 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider; 8 import org.springframework.security.core.Authentication; 9 import org.springframework.security.core.AuthenticationException; 10 import org.springframework.security.core.userdetails.UserDetails; 11 import org.springframework.security.core.userdetails.UserDetailsService; 12 import org.springframework.security.crypto.password.PasswordEncoder; 13 import org.springframework.stereotype.Component; 14 15 import javax.annotation.Resource; 16 import java.util.Objects; 17 18 /** 19 * @Description: AuthenticationManagerBuilder中的AuthenticationProvider是进行认证的核心 20 * @author: zhoum 21 * @Date: 2018-11-23 22 * @Time: 9:11 23 */ 24 @Component("myCustomerAuthenticationProvider") 25 public class CustomerAuthenticationProvider implements AuthenticationProvider { 26 27 @Resource(name = "signUserDetailService") 28 private UserDetailsService userDetailsService; 29 30 public static final Logger LOGGER = LoggerFactory.getLogger(CustomerAuthenticationProvider.class); 31 32 /** 33 *authentication是前台拿过来的号码密码bean 主要验证流程代码 注意这儿懒得用加密验证!!! 34 */ 35 @Override 36 public Authentication authenticate(Authentication authentication) throws AuthenticationException { 37 LOGGER.info("用户输入的用户名是:" + authentication.getName()); 38 LOGGER.info("用户输入的密码是:" + authentication.getCredentials()); 39 // 根据用户输入的用户名获取该用户名已经在服务器上存在的用户详情,如果没有则返回null 40 UserDetails userDetails = userDetailsService.loadUserByUsername(authentication.getName()); 41 try { 42 LOGGER.info("服务器上已经保存的用户名是:" + userDetails.getUsername()); 43 LOGGER.info("服务器上保存的该用户名对应的密码是: " + userDetails.getPassword()); 44 LOGGER.info("服务器上保存的该用户对应的权限是:" + userDetails.getAuthorities()); 45 if(authentication.getCredentials().equals(userDetails.getPassword())){ 46 //验证成功 将返回一个UsernamePasswordAuthenticaionToken对象 47 LOGGER.info("LOGIN SUCCESS !!!!!!!!!!!!!!!!!!!"); 48 //分别返回用户实体 输入的密码 以及用户的权限 49 return new UsernamePasswordAuthenticationToken(userDetails,authentication.getCredentials(),userDetails.getAuthorities()); 50 } 51 } catch (Exception e){ 52 LOGGER.error("author failed, -------------------the error message is:-------- " + e); 53 throw e; 54 } 55 //如果验证不同过则返回null或者抛出异常 56 return null; 57 } 58 59 /** 60 * 61 **/ 62 @Override 63 public boolean supports(Class> aClass) { 64 return true; 65 } 66 }
这儿会返回一个UsernamePasswordAuthenticationToken对象,
(2)继承AbstractUserDetailsAuthenticationProvider抽象类 这儿主要是复写取user和验证流程 这儿直接用了DaoAuthenticationProvider中的验证代码 有兴趣的可以自己重新写验证代码
1 import org.slf4j.Logger; 2 import org.slf4j.LoggerFactory; 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.security.authentication.AuthenticationProvider; 5 import org.springframework.security.authentication.BadCredentialsException; 6 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider; 8 import org.springframework.security.core.Authentication; 9 import org.springframework.security.core.AuthenticationException; 10 import org.springframework.security.core.userdetails.UserDetails; 11 import org.springframework.security.core.userdetails.UserDetailsService; 12 import org.springframework.security.crypto.password.PasswordEncoder; 13 import org.springframework.stereotype.Component; 14 15 import javax.annotation.Resource; 16 import java.util.Objects; 17 18 /** 19 * @Description: AuthenticationManagerBuilder中的AuthenticationProvider是进行认证的核心 20 * @author: zhoum 21 * @Date: 2018-11-23 22 * @Time: 9:11 23 */ 24 @Component("myCustomerAuthenticationProvider") 25 public class CustomerAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { 26 27 @Resource(name = "signUserDetailService") 28 private UserDetailsService userDetailsService; 29 30 @Autowired 31 private PasswordEncoder passwordEncoder; 32 33 /** 34 * 手动实现认证 35 */ 36 @Override 37 protected void additionalAuthenticationChecks(UserDetails userDetails , UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { 38 if ( authentication.getCredentials() == null ) { 39 this.logger.debug("Authentication failed: no credentials provided"); 40 throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials" , "Bad credentials")); 41 } else { 42 String presentedPassword = authentication.getCredentials().toString(); 43 if ( !this.passwordEncoder.matches(presentedPassword , userDetails.getPassword()) ) { 44 this.logger.debug("Authentication failed: password does not match stored value"); 45 throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials" , "Bad credentials")); 46 } 47 } 48 } 49 50 /** 51 * 手动加载user 52 */ 53 @Override 54 protected UserDetails retrieveUser(String s , UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException { 55 return userDetailsService.loadUserByUsername(s); 56 } 57 }
注意4:configure(HttpSecurity http)会覆盖ResourceServerConfig中的配置
4.4 配置ajax跨域
1 import org.springframework.context.annotation.Bean; 2 import org.springframework.context.annotation.Configuration; 3 import org.springframework.web.cors.CorsConfiguration; 4 import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 5 import org.springframework.web.filter.CorsFilter; 6 7 /** 8 * @Description: 9 * @author: zhoum 10 * @Date: 2018-11-22 11 * @Time: 17:09 12 */ 13 @Configuration 14 public class WebOrignAllowConfig { 15 /** 16 * @return org.springframework.web.filter.CorsFilter 17 * @Author zhoum 18 * @Description 解决跨域问题 19 * @Date 17:10 2018-11-22 20 * @Param [] 21 **/ 22 @Bean 23 public CorsFilter corsFilter() { 24 final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource(); 25 final CorsConfiguration corsConfiguration = new CorsConfiguration(); 26 corsConfiguration.setAllowCredentials(true); 27 corsConfiguration.addAllowedOrigin("*"); 28 corsConfiguration.addAllowedHeader("*"); 29 corsConfiguration.addAllowedMethod("*"); 30 urlBasedCorsConfigurationSource.registerCorsConfiguration("/**" , corsConfiguration); 31 return new CorsFilter(urlBasedCorsConfigurationSource); 32 } 33 }
5. 其他项目类即根据User的bean创建出UserController UserService UserMapper等 代码可以查看git上的源码
6. 项目的属性配置 application.yml
spring: #色彩日志输出 output: ansi: enabled: always datasource: username: root password: 123456 url: jdbc:mysql://localhost:3306/tjfx?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&allowPublicKeyRetrieval=true type: com.alibaba.druid.pool.DruidDataSource #连接池配置 driverClassName: com.mysql.jdbc.Driver # 初始化大小,最小,最大 initialSize: 5 minIdle: 5 maxActive: 20 # 配置获取连接等待超时的时间 maxWait: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 timeBetweenEvictionRunsMillis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false # 打开PSCache,并且指定每个连接上PSCache的大小 poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 20 # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 filters: stat,log4j # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 mybatis: mapper-locations: classpath*:**/mappers/*.xml type-aliases-package: cn.com.megalith.domain.entity configuration: map-underscore-to-camel-case: true use-generated-keys: true use-column-label: true cache-enabled: true call-setters-on-nulls: true logging: level: root: info org: springframework: info server: port: 8081
7.启动类SecurityApplication
1 import org.mybatis.spring.annotation.MapperScan; 2 import org.springframework.boot.SpringApplication; 3 import org.springframework.boot.autoconfigure.SpringBootApplication; 4 import org.springframework.transaction.annotation.EnableTransactionManagement; 5 6 @SpringBootApplication 7 @MapperScan("cn.com.megalith.dao") 8 @EnableTransactionManagement 9 public class SecurityApplication { 10 11 public static void main(String[] args) { 12 SpringApplication.run(SecurityApplication.class , args); 13 }
8.项目整体效果如下(不要在意红色,编辑器故障了)
9.进行测试
(1) 获取token
注意:其中的client_id 与 client_secret即在oauth_client_details中的数据 这儿是我自己手动插入的一条 一般在申请别的网站的授权时应该会得到这个 client_secret是md5加密后的结果
如果号码密码正确则会返回 红色框内即为得到的token
如果密码错误 则会返回刚自定义的错误返回结果
(2)进行方法验证
1 @RestController 2 @RequestMapping("/user") 3 public class UserController { 4 5 @GetMapping("/current") 6 @Secured("ROLE_AA") 7 public String getCurrentUser(Principal principal) { 8 System.out.println(principal); 9 return "111"; 10 } 11 }
访问方式:记得加上token 不然会验证不通过
且principal即为获取到的当前用户的对象 具体结构如下
验证失败则会返回
如果无权限则会返回
以上为授权认证端与资源服务端在同一项目时,如果为不同的项目则资源服务的项目可以单独如下创建项目
项目customer_simple(资源服务项目)
1.创建项目
与创建上面授权项目完全类似,依赖一样,所以略过
2.配置类
(1)ResourceServiceConfig
1 import org.springframework.context.annotation.Configuration; 2 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 3 import org.springframework.security.config.annotation.web.builders.HttpSecurity; 4 import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 5 import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; 6 import org.springframework.web.cors.CorsConfiguration; 7 8 import javax.servlet.http.HttpServletResponse; 9 10 /** 11 * @Description: 配置资源服务器 12 * @author: zhoum 13 * @Date: 2018-11-26 14 * @Time: 15:08 15 */ 16 @Configuration 17 @EnableResourceServer 18 @EnableGlobalMethodSecurity(securedEnabled = true) 19 public class ResourceServiceConfig extends ResourceServerConfigurerAdapter { 20 21 @Override 22 public void configure(HttpSecurity http) throws Exception { 23 http 24 .csrf().disable() 25 .exceptionHandling() 26 .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED)) 27 .and() 28 .authorizeRequests() 29 .antMatchers("/**").permitAll() 30 //跨域配置 31 .and().cors().configurationSource(request -> { 32 CorsConfiguration corsConfiguration = new CorsConfiguration(); 33 corsConfiguration.addAllowedHeader("*"); 34 corsConfiguration.addAllowedOrigin(request.getHeader("Origin")); 35 corsConfiguration.addAllowedMethod("*"); 36 corsConfiguration.setAllowCredentials(true); 37 corsConfiguration.setMaxAge(3600L); 38 return corsConfiguration; 39 }); 40 } 41 }
(2) 跨域配置类
1 import org.springframework.context.annotation.Bean; 2 import org.springframework.context.annotation.Configuration; 3 import org.springframework.web.cors.CorsConfiguration; 4 import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 5 import org.springframework.web.filter.CorsFilter; 6 7 /** 8 * @Description: 9 * @author: zhoum 10 * @Date: 2018-11-22 11 * @Time: 17:09 12 */ 13 @Configuration 14 public class WebOrignAllowConfig { 15 /** 16 * @return org.springframework.web.filter.CorsFilter 17 * @Author zhoum 18 * @Description 解决跨域问题 19 * @Date 17:10 2018-11-22 20 * @Param [] 21 **/ 22 @Bean 23 public CorsFilter corsFilter() { 24 final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource(); 25 final CorsConfiguration corsConfiguration = new CorsConfiguration(); 26 corsConfiguration.setAllowCredentials(true); 27 corsConfiguration.addAllowedOrigin("*"); 28 corsConfiguration.addAllowedHeader("*"); 29 corsConfiguration.addAllowedMethod("*"); 30 urlBasedCorsConfigurationSource.registerCorsConfiguration("/**" , corsConfiguration); 31 return new CorsFilter(urlBasedCorsConfigurationSource); 32 } 33 }
(3)application.yml配置
1 spring: 2 #色彩日志输出 3 output: 4 ansi: 5 enabled: always 6 datasource: 7 username: root 8 password: 123456 9 url: jdbc:mysql://localhost:3306/tjfx?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&allowPublicKeyRetrieval=true 10 type: com.alibaba.druid.pool.DruidDataSource 11 #连接池配置 12 driverClassName: com.mysql.jdbc.Driver 13 # 初始化大小,最小,最大 14 initialSize: 5 15 minIdle: 5 16 maxActive: 20 17 # 配置获取连接等待超时的时间 18 maxWait: 60000 19 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 20 timeBetweenEvictionRunsMillis: 60000 21 # 配置一个连接在池中最小生存的时间,单位是毫秒 22 minEvictableIdleTimeMillis: 300000 23 validationQuery: SELECT 1 FROM DUAL 24 testWhileIdle: true 25 testOnBorrow: false 26 testOnReturn: false 27 # 打开PSCache,并且指定每个连接上PSCache的大小 28 poolPreparedStatements: true 29 maxPoolPreparedStatementPerConnectionSize: 20 30 # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 31 filters: stat,log4j 32 # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 33 connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 34 35 mybatis: 36 mapper-locations: classpath*:**/mappers/*.xml 37 type-aliases-package: cn.com.megalith.domain.entity 38 configuration: 39 map-underscore-to-camel-case: true 40 use-generated-keys: true 41 use-column-label: true 42 cache-enabled: true 43 call-setters-on-nulls: true 44 #spring security oauth2配置 45 security: 46 oauth2: 47 #token检查的授权端url 48 authorization: 49 check-token-access: http://127.0.01:8081/oauth/check_token 50 #对应的注册码与注册密码 51 client: 52 id: 1 53 client-secret: 2 54 authentication-scheme: form 55 #获得授权端的当前用户信息url 56 resource: 57 user-info-uri: http://127.0.01:8081/user/me 58 id: oa 59 60 logging: 61 level: 62 root: info 63 org: 64 springframework: info 65 server: 66 port: 8082
这儿注意http://127.0.01:8081/user/me即授权服务端中的一个接口 需要提供,返回用户信息,代码在授权端项目的UserController下添加如下方法即可
1 /** 2 *客户端根据token获取用户 3 */ 4 @RequestMapping("/me") 5 public Principal user2(OAuth2Authentication principal) { 6 return principal; 7 }
(4)启动类配置
1 import org.mybatis.spring.annotation.MapperScan; 2 import org.springframework.boot.SpringApplication; 3 import org.springframework.boot.autoconfigure.SpringBootApplication; 4 5 @SpringBootApplication 6 @MapperScan("cn.com.megalith.dao") 7 public class CustomerSimpleApplication { 8 9 public static void main(String[] args) { 10 SpringApplication.run(CustomerSimpleApplication.class , args); 11 } 12 }
2.项目的其他辅助类主要为User的controller,service,dao,entity等 源码里有
3.项目弄好后结构如下
4.测试
依旧使用UserController,代码如下
1 @RestController 2 @RequestMapping("/user") 3 public class UserController { 4 5 @GetMapping("/current") 6 @Secured("ROLE_AA") 7 public String getCurrentUser(Principal principal) { 8 System.out.println(principal); 9 return "111"; 10 } 11 }
测试方法如下,使用上面获得token的方式获取token后访问如下
注意这儿变成了8082端口,说明是另外的一个资源服务端的项目
验证成功
验证失败后返回
如果没有权限则会返回
到此基于springboot spring security +oauth2.0的密码模式授权 且授权端与资源提供端分离的模式就完成了 有问题留言
项目的源码地址为 https://github.com/hetutu5238/zmc_security_oauth2.git security_simple为认证授权端 customer_simple为资源服务端
项目的mysql脚本在 security_simple下的resource/sql/tjfx.sql
在获取token时 如何返回如下格式的数据结构?
1.定义返回类
1 public class Resultimplements Serializable { 2 3 private String message ; 4 5 private Integer code ; 6 7 private T result; 8 9 10 public static Result ok(T data) { 11 Result r = new Result<>(); 12 r.setMessage("ok"); 13 r.setCode(HttpStatus.OK.value()); 14 r.setResult(data); 15 return r; 16 } 17 18 public String getMessage() { 19 return message; 20 } 21 22 public void setMessage(String message) { 23 this.message = message; 24 } 25 26 public Integer getCode() { 27 return code; 28 } 29 30 public void setCode(Integer code) { 31 this.code = code; 32 } 33 34 public T getResult() { 35 return result; 36 } 37 38 public void setResult(T result) { 39 this.result = result; 40 } 41 }
2.pom.xml新增
1 <dependency> 2 <groupId>org.springframework.bootgroupId> 3 <artifactId>spring-boot-starter-aopartifactId> 4 dependency>
3.增加配置类 项目源码我注掉了 有需要的可以打开
1 @Aspect 2 @Component 3 public class CodeAspect { 4 5 @Pointcut("execution(public * org.springframework.security.oauth2.provider..*.*TokenEndpoint.postAccessToken(..))") 6 public void excudeService() { 7 } 8 9 @Around("excudeService()") 10 public Object doAround(ProceedingJoinPoint pjp) throws Throwable { 11 12 Object result = pjp.proceed(); 13 if ( result instanceof ResponseEntity ){ 14 ResponseEntity res = (ResponseEntity)result; 15 if ( res.getStatusCode() .equals(HttpStatus.OK) ){ 16 Object body = res.getBody(); 17 if ( body instanceof DefaultOAuth2AccessToken ){ 18 DefaultOAuth2AccessToken data = (DefaultOAuth2AccessToken)body; 19 ResponseEntity codeResult = new ResponseEntity(Result.ok(data.getValue()),HttpStatus.OK); 20 return codeResult; 21 } 22 } 23 24 } 25 return result; 26 } 27 }
这样即可